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, 2025Tempest 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!