Tempest is still a work in progress . Visit our GitHub or Discord

Static Pages

Tempest comes with a built-in static site generator. When a controller action is tagged with #[StaticPage] , it can be compiled by Tempest as a static HTML page. These pages can then directly be served via your webserver.

final readonly class HomeController
{
    #[StaticPage]
    #[Get('/')]
    public function home(): View
    {
        return view('home');
    }
}

Compiling all static pages is done with the static:generate command:

./tempest static:generate

You can also remove all statically generated pages with the static:clean command:

./tempest static:clean

Data providers

Since most pages require some form of dynamic data, static pages can be assigned a data provider, which will generate multiple pages for one controller action.

Let's take a look at the controller action for this docs website:

final readonly class DocsController
{
    #[StaticPage(DocsDataProvider::class)]
    #[Get('/{category}/{slug}')]
    public function show(string $category, string $slug, ChapterRepository $chapterRepository): View
    {
        return new DocsView(
            chapterRepository: $chapterRepository,
            currentChapter: $chapterRepository->find($category, $slug),
        );
    }
}

In this case, the #[StaticPage] attribute gets a reference to the DocsDataProvider , which implements the \Tempest\Http\DataProvider interface:

final readonly class DocsDataProvider implements DataProvider
{
    public function provide(): Generator
    {
        // …
    }
}

A data provider's goal is to generate multiple pages for one controller action. It does so by yielding an array of controller action parameters for every page the needs to be generated. In case of the docs controller, the action needs a $category and $slug , as well as a $chapterRepository . That $chapterRepository is injected by the container, so we don't need to worry about it here. What we do need to provide is a category and slug for each page we want to generate.

In other words: we want to generate a page for every docs chapter. We can use the ChapterRepository to get a list of all available chapters. Eventually, our data provider looks like this:

final readonly class DocsDataProvider implements DataProvider
{
    public function __construct(
        private ChapterRepository $chapterRepository
    ) {}

    public function provide(): Generator
    {
        foreach ($this->chapterRepository->all() as $chapter) {
            // Yield an array of parameters that should be passed to the controller action,
            yield [
                'category' => $chapter->category,
                'slug' => $chapter->slug,
            ];
        }
    }
}

The only thing left to do is to generate the static pages:

./tempest static:generate

- /framework/01-getting-started > /Users/brent/Dev/tempest-docs/public/framework/01-getting-started.html
- /framework/02-the-container > /Users/brent/Dev/tempest-docs/public/framework/02-the-container.html
- /framework/03-controllers > /Users/brent/Dev/tempest-docs/public/framework/03-controllers.html
- /framework/04-views > /Users/brent/Dev/tempest-docs/public/framework/04-views.html
- /framework/05-models > /Users/brent/Dev/tempest-docs/public/framework/05-models.html
- /* … */

Server setup

Tempest will generate static HTML pages, but it won't take care of serving them. The goal of these pages is to not run PHP at all, and have your web server serve them directly.

Nginx

location / {
    try_files $uri.html $uri $uri/ /index.php?$query_string;
}

Other web servers

If you use other webservers and want their config listed on this page, feel free to send a pull request to the docs site.

Deployments

Finally, keep in mind that static pages should be regenerated on every deploy. You should add the ./tempest static:generate command in your deployment pipeline.