The final alpha release

Tempest alpha 6 is released, we'll talk about Tempest's future and highlight the most important new features in this release

by Brent on March 24, 2025

Tempest alpha 6 is here: the final alpha release for Tempest. The next one will be beta 1, and from there on out it'll be a straight line to a stable 1.0 release! This final alpha release brings a bunch of new features, improvements, and fixes; this time by 8 contributors in total. I'll walk you through the highlights, but I want to start by talking about the future plans.

composer create-project tempest/app:1.0-alpha.6 <name>

Tempest's future

Tempest's first alpha release was tagged half a year ago. It's amazing to see that, since then, 35 people have contributed to the project, and alpha 6 is so different and so much more feature-rich than alpha 1. At the same time, it's important to realize that we cannot stay in alpha for years. There is so much more to be done, and Tempest is far from "ready", but there's a real danger of ending in an infinite "alpha limbo", where we keep adding awesome stuff, but never get to actually release something for real.

I want Tempest to be real. And real things aren't perfect. They don't have to be perfect. That's why we're now moving towards 1.0. There'll be one or two beta releases after this one, but that's it. The goal of these beta releases will be to fix some final bugs, review the docs, do some touch-ups here and there. The goal of 1.0 isn't to be perfect, it's to be real.

There is one thing we've agreed on with the core team: we'll mark some components and features as experimental. These experimental features can still change after 1.0 in minor releases. This gives us a bit more freedom to iron out the kinks, but also gives Tempest users some more certainty about what's changing and what not. The goal is to have this list ready before beta.1, and then we'll have some more insights in whether there are possibly future breaking changes or not.

All of that being said, let's talk about what's new in Tempest alpha 6!

tempest/view updates

We start with tempest/view, which has gotten a lot of love this release. We've fixed a wide range of edge cases and bugs (many were caused because we switched to PHP's built-in HTML 5 spec compliant parser), but we also added a whole range of cool new features.

x-template

There's a new <x-template> component which will only render its contents so that you don't have to wrap that content into another element. For example, the following:

<x-template :foreach="$posts as $post">
    <div>{{ $post->title }}</div>
    <span>{{ $post->description }}</span>
</x-template>

Will be compiled to:

<div>Post A</div>
<span>Description A</span>
<div>Post B</div>
<span>Description B</span>
<div>Post C</div>
<span>Description C</span>

Dynamic slots and attributes

View components now have direct access to the $slots and $attributes variables, they give a lot more flexibility when building reusable components.

<x-component name="x-tabs">
    <span :foreach="$attributes['tags'] as $tag">{{ $tag }}</span>

    <x-codeblock :foreach="$slots as $slot">
        <h1>{{ $slot->name }}</h1>

        <h2>{{ $slot->attributes['language'] }}</h2>

        <div>{!! $slot->content !!}</div>
    </x-codeblock>
</x-component>

<x-tabs :tags="['a', 'b', 'c']">
    <x-slot name="php" language="PHP">This is the PHP tab</x-slot>
    <x-slot name="js" language="JavaScript">This is the JS tab</x-slot>
    <x-slot name="html" language="HTML">This is the HTML tab</x-slot>
</x-tabs>

Attribute improvements

Attributes are now more flexible. For example, the :class and :style expression attributes will be merged automatically with their normal counterpart:

<div class="bg-red-500" :class="$otherClasses"></div>

There's support for fallthrough attributes: any class, style or id attribute on a view component will be automatically placed and merged on the first child of that component:

<x-component name="x-with-fallthrough-attributes">
    <div class="bar"></div>
</x-component>

<x-with-fallthrough-attributes class="foo"></x-with-fallthrough-attributes>

<!-- <div class="bar foo"></div> -->

Relative view paths

There's support for relative view paths when returned from controllers:

use Tempest\Router\Get;
use Tempest\View\View;
use function Tempest\View;

final class BookController
{
    #[Get('/books')]
    public function index(): View
    {
        // book_index.view.php can be in the same folder as this directory
        return view('book_index.view.php');
    }
}

View processors

View processors can add data in bulk across multiple views:

use Tempest\View\View;
use Tempest\View\ViewProcessor;

final class StarCountViewProcessor implements ViewProcessor
{
    public function __construct(
        private readonly Github $github,
    ) {}

    public function process(View $view): View
    {
        if (! $view instanceof WithStarCount) {
            return $view;
        }

        return $view->data(starCount: $this->github->getStarCount());
    }
}

File-based view components

View components can now be discovered by file name:

<!-- x-base.view.php -->

<html>
    <head></head>
    <body>
        <x-slot/>
    </body>
</html>
<x-base>
  Hello World!
</x-base>

The x-icon component

And finally, there's a new <x-icon> component, added by Nicolas, which adds built-in support for Iconify icons:

<x-icon name="tabler:rss" class="shrink-0 size-4" />

Primitive helpers

Enzo has made some pretty significant changes to our arr() and str() helpers: there are now two variants available: MutableString and ImmutableString, as well as MutableArray and ImmutableArray. The helper functions still use the immutable version by default:

use function Tempest\Support\str;

$excerpt = str($content)
    ->excerpt(
        from: $previous->getLine() - 5,
        to: $previous->getLine() + 5,
        asArray: true,
    )
    ->map(function (string $line, int $number) use ($previous) {
        return sprintf(
            "%s%s | %s",
            $number === $previous->getLine() ? '> ' : '  ',
            $number,
            $line
        );
    })
    ->implode(PHP_EOL);

We've also made all helper functions available directly as a function:

use function Tempest\Support\Arr\undot;

$data = undot([
    'author.name' => 'Brent',
    'author.email' => 'brendt@stitcher.io',
]);

There's also a new IsEnumHelper trait which adds a bunch of convenient methods for enums:

use Tempest\Support\IsEnumHelper;

enum MyEnum
{
    use IsEnumHelper;

    case FOO;
    case BAR;
}

MyEnum::FOO->is(MyEnum::BAR);
MyEnum::names();

// …

Mapper improvements

We've changed the API of the mapper slightly to be more consistent. map()->with() can now be combined both with ->to() and ->do():

use function Tempest\map;

map($input)->with(BookMapper::class)->to(Book::class);
map($input)->with(BookMapper::class)->do();

There are also two new methods to map straight to json and arrays:

use function Tempest\map;

map($book)->toJson();
map($book)->toArray();

We also made it possible to add dynamic casters and serializers for non-built in types:

use Tempest\Mapper\Casters\CasterFactory;
use Tempest\Mapper\Casters\SerializerFactory;

$container->get(CasterFactory::class)->addCaster(Carbon::class, CarbonCaster::class);
$container->get(SerializerFactory::class)->addSerializer(Carbon::class, CarbonSerializer::class);

Vite support

Enzo has worked hard to add Vite support, with the option to install Tailwind as well. It's as simple as running the Vite installer:

~ ./tempest install vite

Next, add <x-vite-tags />, in the <head> of your template:

<html lang="en" class="h-dvh flex flex-col">
  <head>
      <!-- … -->

      <x-vite-tags/>
  </head>
  <body>
      <x-slot/>
  </body>
</html>

And run your dev server:

~ bun run dev

# or npm run dev

Done!

Database improvements

Vincent has simplified database configs, instead of having a single DatabaseConfig object with a connection, we've created a DatabaseConfig interface, which each driver now implements:

// app/Config/database.config.php

use Tempest\Database\Config\MysqlConfig;
use function Tempest\env;

return new MysqlConfig(
    host: env('DB_HOST'),
    port: env('DB_PORT'),
    username: env('DB_USERNAME'),
    password: env('DB_PASSWORD'),
    database: env('DB_DATABASE'),
);

Next, Matt added support for a #[Virtual] property, which excludes models fields from the model query:

use Tempest\Database\Virtual;
use Tempest\Database\DatabaseModel;
use Tempest\Database\IsDatabaseModel;

class Book implements DatabaseModel
{
    use IsDatabaseModel;

    // …

    public DateTimeImmutable $publishedAt;

    #[Virtual]
    public DateTimeImmutable $saleExpiresAt {
        get => $this->publishedAt->add(new DateInterval('P5D'));
    }
}

New website

One last thing to mention — you might have noticed it already — we've completely redesigned the Tempest website! A big shout-out to Enzo who made a huge effort to get it ready! Of course, there a lot more changes with this release, you can check the full changelog here.

In closing

That's it for this release, I hope you're excited to give Tempest a try, because your input is so valuable. Don't hesitate to open issues and join our Discord server, we'd love to hear from you!