Written by Brent on November 25, 2024 — Back
Once again a month has passed, and we're tagging a new alpha release of Tempest. This time we have over 70 merged pull requests by 12 contributors. We've also created a backlog of issues to tackle before 1.0, it's a fast-shrinking list!
I'll share some more updates about the coming months at the end of this post, but first let's take a look at what's new and changed in Tempest alpha.4!
Async commands are a new feature in Tempest that allow developers to handle tasks in a background process. Tempest already came with a command bus before this release, and running commands asynchronously is as easy as adding the #[AsyncCommand]
attribute to a command class.
// app/SendMail.php use Tempest\CommandBus\AsyncCommand; #[AsyncCommand] final readonly class SendMail { public function __construct( public string $to, public string $body, ) {} }
Dispatching async commands is done exactly the same as dispatching normal commands:
use function Tempest\command; command(new SendMail( to: 'brendt@stitcher.io', body: 'Hello!' ));
Finally, in order to actually run the associated command handler after an async command has been dispatched, you'll have to run ./tempest command:monitor
. This console command should always be running, so you'll need to configure it as a daemon on your production server.
~ ./tempest command:monitor
Monitoring for new commands. Press ctrl+c to stop.
While the core functionality of async command handling is in place, we plan on building more features like multi-driver support and balancing strategies on top of it in the future.
Before this release, discovery cache could either be on or off. This wasn't ideal for local development environments where you'd potentially have lots of vendor packages that have to be discovered as well. Partial discovery cache solves this by caching vendor code, but no project code.
Partial discovery cache is enabled via an environment variable:
# .env DISCOVERY_CACHE=partial
This caching strategy comes with one additional requirement: it will only work whenever the partial cache has been generated. This is done via the discovery:generate
command:
~ ./tempest discovery:generate Clearing existing discovery cache… Discovery cached has been cleared Generating new discovery cache… (cache strategy used: partial) Done 111 items cached
The same manual generation is now also required when deploying to production with full discovery cache enabled. You can read more about automating this process in the docs. Finally, if you're interested in some more behind-the-scenes info and benchmarks, you can check out the GitHub issue.
Guillaume has laid the groundwork for a wide variaty of make:
commands! The first ones are already added: make:controller
, make:model
, make:request
, and make:response
. There are many more to come!
~ ./tempest make:controller FooController Where do you want to save the file "FooController"? app/FooController.php Controller successfully created at "app/FooController.php".
If you're interested in helping you, you can check out the list of TODO make:
commands here. We're always welcoming to people who want to contribute!
Aidan added the first iteration of our filesystem component. The next step is to implement it all throughout the framework — there are many places where we're relying on PHP's suboptimal built-in file system API that could be replaced.
use Tempest\Filesystem\LocalFilesystem; $fs = new LocalFilesystem(); $fs->ensureDirectoryExists(root_path('.cache/discovery/partial/'));
#[Inject]
AttributeThe #[Inject]
attribute can be used to tell the container that a property's value should be injected right after construction. This feature is especially useful with framework-provided traits, where you don't want to occupy the constructor within the trait.
// Tempest/Console/src/HasConsole.php use Tempest\Container\Inject; trait HasConsole { #[Inject] private Console $console; // … }
You can read more about when and when not to use this feature in the docs.
config:show
CommandSamir added a new config:show
command that dumps all loaded config in different formats.
~ ./tempest config:show { "…/vendor/tempest/framework/src/Tempest/Log/src/Config/logs.config.php": { "@type": "Tempest\\Log\\LogConfig", "channels": [], "prefix": "tempest", "debugLogPath": null, "serverLogPath": null }, "…/vendor/tempest/framework/src/Tempest/Auth/src/Config/auth.config.php": { "@type": "Tempest\\Auth\\AuthConfig", "authenticatorClass": "Tempest\\Auth\\SessionAuthenticator", "userModelClass": "Tempest\\Auth\\Install\\User" }, // … }
This command can come in handy for debugging, as well as for future IDE integrations.
We made a small change to all middleware interfaces (HTTP, console, event bus, and command bus middlewares). The $callable
argument of a middleware is now always properly typed, so that you get autocompletion in your IDE without having to add doc blocks.
As a comparison, this is what you had to write before:
use Tempest\Router\HttpMiddleware; use Tempest\Router\Request; use Tempest\Router\Response; class MyMiddleware implements HttpMiddleware { public function __invoke(Request $request, callable $next): Response { /** @var \Tempest\Router\Response $response */ $response = $next($request); // … } }
And now you can write this:
use Tempest\Router\HttpMiddleware; use Tempest\Router\Request; use Tempest\Router\Response; use Tempest\Router\HttpMiddlewareCallable; class MyMiddleware implements HttpMiddleware { public function __invoke(Request $request, HttpMiddlewareCallable $next): Response { $response = $next($request); // … } }
Next, Vincent made a lot of improvements to the router alongside contributions by many others. There's too much to show in detail, so I'll make another list with the highlights:
We added boolean attribute support in tempest/view:
<option :value="$value" :selected="$selected">{{ $name }}</option>
Matthieu added support for json
and set
data types in the ORM:
use Tempest\Database\Migration; use Tempest\Database\QueryStatement; use Tempest\Database\QueryStatements\CreateTableStatement; class BookMigration implements Migration { public function up(): QueryStatement|null { return CreateTableStatement::forModel(Book::class)) ->set('setField', values: ['foo', 'bar'], default: 'foo') ->json('jsonField', default: '{"default": "foo"}'); } // … }
And finally, let's look at tempest/console: we added a range of small features to our console component:
Besides all those smaller changes, Enzo is also working on a complete overhaul of the dynamic component system, it's still a work in progress, but it is looking great! You can check out the full PR (with examples) here.
And that's it! Well, actually, lots more things were done, but it's way too much to list in one blog post. These were the highlights, but you can also read the full changelog if you want to know all the details.
Once again, I'm amazed by how much the community is helping out with Tempest, at such an early stage of its lifecycle. I'm also looking forward to what's next: we plan to release alpha.5 somewhere mid-January. With it, we hope to support PHP 8.4 at the minimum, and update the whole framework to use new PHP 8.4 features wherever it makes sense. I blogged about the "why" behind that decision a while ago, if you're interested: https://stitcher.io/blog/php-84-at-least.
PHP 8.4 is one of the last big things on our roadmap that's blocking a 1.0 release, so… 2025 will be a good year. If you want to be kept in the loop, Discord is the place to be. If you're interested in contributing, then make sure to head over to the alpha.5 and pre-1.0 milestones. They give a pretty accurate overview of what's still on our plate before we tag the first stable release of Tempest. Exiting times!
Until next time!