Tempest Framework Documentation
repository·3.x·Indexed 24 days ago
https://github.com/tempestphp/tempest-frameworkA community-driven, modern PHP framework featuring a 'zero config, zero overhead' approach. Tempest leverages PHP attributes for routing, CLI commands, and event handling, utilizing a discovery mechanism to eliminate boilerplate and manual configuration. It supports flexible project structures, extensible core components, and modern PHP features like property hooks and proxy objects.
What's inside Tempest
- Tempest is a modern PHP framework designed to minimize framework-related boilerplate, allowing developers to focus on application logic. It follows a "zero config, zero overhead" philosophy, utilizing PHP attributes for routing, command definition, and event handling.
Understand Tempest's core philosophy and features
3.xTempest is a modern PHP framework designed to be lightweight, powerful, and unobtrusive. It aims to provide a 'sweet spot' between the robustness of Symfony and the eloquence of Laravel by embracing modern PHP features and staying close to vanilla PHP.
Key characteristics include:
- Modern PHP First: Uses features like property hooks, attributes, and proxy objects.
- Unopinionated Structure: Does not force a specific project structure (MVC, DDD, Hexagonal, etc.); it works with your existing organization out of the box.
- Discovery-Driven: Uses a 'Discovery' mechanism to automatically handle routing, console commands, view components, event listeners, and more without manual configuration.
- Extensible: Almost any part of the framework can be replaced by implementing an interface and registering it in the container.
What is Discovery in Tempest
3.xDiscovery is Tempest's mechanism for automatically locating application components like controller actions, event handlers, and console commands without manual registration. It scans the codebase and uses composer metadata to include both your application code and package dependencies. Tempest determines the purpose of code by analyzing file names, attributes, interfaces, and return types.
final readonly class HomeController { #[Get(uri: '/home')] public function __invoke(): View { return view('home.view.php'); } }Prevent classes from being discovered by Tempest
3.xYou can prevent Tempest from automatically discovering specific classes using two methods:
- Attribute-based: Mark a class with the
#[Tempest\Discovery\SkipDiscovery]attribute. This is useful for classes you want to use internally or publish via an installer, but don't want Tempest to automatically register. - Composer-based: Use the
extra.tempest.ignorearray incomposer.jsonto exclude specific file paths. This is recommended when dealing with optional dependencies to prevent Reflection errors when those dependencies are missing.
use Tempest\Discovery\SkipDiscovery; #[SkipDiscovery] final readonly class UserMigration implements Migration { // … }{ "extra": { "tempest": { "ignore": [ "src/OptionalDependency.php" ] } } }- Attribute-based: Mark a class with the
How configuration works in Tempest
3.xIn Tempest, configuration is represented by objects rather than arrays. This approach provides superior developer experience through static analysis and autocompletion in code editors. Configuration objects are registered as singletons in the container, meaning once they are instantiated, the same instance is used throughout the application lifecycle. You can overwrite default framework configurations (like switching from SQLite to PostgreSQL) by providing your own configuration files.Define events as data classes or enumerations
3.xEvents in Tempest are used to decouple components. You can define them in two ways:
- Data Classes: Best for events that need to carry specific payload information. It is recommended to use
final readonlyclasses with no logic. - Enumerations: Best for simple state changes where no extra data is required. This is highly recommended for a better developer experience.
Events can be any scalar value, but classes and enums are the standard.
// Data class approach final readonly class AircraftRegistered { public function __construct( public string $registration, ) {} } // Enumeration approach enum AircraftLifecycle { case REGISTERED; case RETIRED; }- Data Classes: Best for events that need to carry specific payload information. It is recommended to use
Routing and Controller patterns in Tempest
3.xTempest uses PHP attributes to define routes directly on controller methods. This allows for clean, declarative routing without separate configuration files.
Example of a controller using
#[Get]and#[Post]attributes:final class BookController { #[Get('/books/{book}')] public function show(Book $book): Response { return new Ok($book); } #[Post('/books')] public function store(CreateBookRequest $request): Response { $book = map($request)->to(Book::class)->save(); return new Redirect([self::class, 'show'], book: $book->id); } // … }final class BookController { #[Get('/books/{book}')] public function show(Book $book): Response { return new Ok($book); } #[Post('/books')] public function store(CreateBookRequest $request): Response { $book = map($request)->to(Book::class)->save(); return new Redirect([self::class, 'show'], book: $book->id); } }Override validation translation messages
3.xTempest uses MessageFormat 2.0 for localization. You can override default validation messages by providing translation files (e.g.,
validation.en.yml).To use a specific translation key for a property instead of the default rule-based key, apply the
#[\Tempest\Validation\TranslationKey]attribute to the property. This changes the lookup path fromvalidation_error.{rule}tovalidation_error.{rule}.{your_key}.final class Book { #[Rules\HasLength(min: 5, max: 50)] #[TranslationKey('book_management.book_title')] public string $title; } // The validator will look for the key: // validation_error.has_length.book_management.book_titleManage asset entrypoints
3.xTempest supports two ways to define entrypoints:
Automatic Discovery: Any file ending with
.entrypoint.{ts,css,js}is automatically discovered and bundled. Example:app/main.entrypoint.tsManual Configuration: If you want to use different naming conventions, create a
app/vite.config.phpfile that returns aTempest\Vite\ViteConfiginstance and define your entrypoints in theentrypointsarray. Paths must be relative to the project root.
Note: If you manually include a specific entrypoint in a view using
<x-vite-tags entrypoint="path/to/file" />, you must also ensure that file is configured as an entrypoint in your Vite configuration for it to be included in the production manifest.return new ViteConfig( entrypoints: [ 'app/main.css', 'app/main.ts', ], );<x-base> <slot name="head"> <x-vite-tags entrypoint="src/Profile/profile.css" /> </slot> </x-base>Use Data Providers to generate multiple static pages
3.xTo generate multiple pages from a single controller action (e.g., a blog post or documentation chapter), assign a Data Provider to the
#[Tempest\Router\StaticPage]attribute.The Data Provider must implement the
\Tempest\Router\DataProviderinterface and itsprovide()method must return aGenerator. This generator shouldyieldan array of parameters that correspond to the controller action's arguments for every page that needs to be generated.Note that dependencies (like repositories) can be injected into the Data Provider's constructor via the container, just like in a controller.
// 1. Define the Data Provider use Tempest\Router\DataProvider; final readonly class ChapterDataProvider implements DataProvider { public function __construct( private ChapterRepository $chapters ) {} public function provide(): Generator { foreach ($this->chapters->all() as $chapter) { yield [ 'category' => $chapter->category, 'slug' => $chapter->slug, ]; } } } // 2. Use it in the Controller use Tempest\Router\Get; use Tempest\Router\StaticPage; use Tempest\View\View; final readonly class ChapterController { #[StaticPage(ChapterDataProvider::class)] #[Get('/{category}/{slug}')] public function show(string $category, string $slug, ChapterRepository $chapters): View { return new ChapterView( repository: $chapters, current: $chapters->find($category, $slug), ); } }Use MessageFormat 2.0 syntax for translations
3.xTempest implements the MessageFormat 2.0 specification. This allows for advanced syntax including variables, pluralization, and custom formatting functions.
Example YAML syntax using a variable and a
datetimefunction with a pattern parameter:today: Today is {$today :datetime pattern=|yyyy/MM/dd|}Automatic fallthrough of HTML attributes
3.xWhen
{html}class,{html}style, or{html}idattributes are provided to a view component, Tempest automatically attempts to apply them to the root node within that component.Example: If
x-button.view.phpcontains:<button class="rounded-md px-2.5 py-1.5 text-sm"> <x-slot /> </button>And you use it like this:
<x-button id="myBtn" style="color: red;" />It renders as:
<button id="myBtn" style="color: red;" class="rounded-md px-2.5 py-1.5 text-sm" />Disabling automatic fallthrough: To prevent automatic application, explicitly define the attributes within the component using expression attributes. For example, using
:idand:classinside the component will stop Tempest from overwriting or automatically applying the passed{html}idor{html}class.<button :id="uniqid(($id ?? 'mybtn') . '_')" :class="$class ?? 'rounded-md px-2.5 py-1.5 text-sm'"> <x-slot /> </button>