TempestTempest

The PHP templating engine that speaks your language

composer require tempest/view --stability alpha

Designed to feel familiar

Tempest View is an extension on the most popular templating engine of all time: HTML. Template inheritance and inclusion are modelled via HTML elements; data binding and control structures are handled with attributes.

<x-base title="Home">
    <x-post :foreach="$this->posts as $post">
        {!! $post->title !!}

        <span :if="$this->showDate($post)">
            {{ $post->date }}
        </span>
        <span :else>
            -
        </span>
    </x-post>
    <div :forelse>
        <p>It's quite empty here…</p>
    </div>
    
    <x-footer />
</x-base>

The Tempest Touch

Tempest's discovery is built-in — that's a given. Just create a .view.php file, and Tempest takes care of the rest. No config needed, your components are available everywhere.

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

<x-component name="x-base">
    <html lang="en">
        <head>
            <title :if="$title ?? null">{{ $title }} | Tempest</title>
            <title :else>Tempest</title>
            
            <x-slot name="styles" />
        </head>

        <body class="relative font-sans antialiased">
            <x-slot/>
        
            <x-slot name="scripts" />
        </body>
    </html>
</x-component>

The perfect blend

Just in case you need that little extra, PHP is there to help you out.

<?php
/** @var \App\Front\BlogPostView $this */

use App\Front\Meta\MetaImageController;
use function Tempest\uri;

$title = 'Click me!';
?>

<a :href="uri(MetaImageController::class, $this->type)">
    {{ $title }}
</a>

We need help!

We would like to have proper IDE support for Tempest View before tagging version 1.0. If you're familiar with implementing LSPs or building IntelliJ plugins, feel free to contact us via Discord or email to discuss the details.

Robust backend components

When anonymous components aren't enough, you can fall back to full-blown PHP implementations. Coming in the future: these components will be the entry point for reactive components similar to Livewire or HTMX.

final readonly class Input implements ViewComponent
{
    public function __construct(
        private Session $session,
    ) {
    }

    public static function getName(): string
    {
        return 'x-input';
    }

    public function compile(ViewComponentElement $element): string
    {
        $name = $element->getAttribute('name');
        $label = $element->getAttribute('label');
        $type = $element->getAttribute('type');
        $default = $element->getAttribute('default');

        $errorHtml = '';

        if ($errors = ($this->session->get(Session::VALIDATION_ERRORS)[$name] ?? null)) {
            $errorHtml = '<div>' . implode('', array_map(
                fn (Rule $failingRule) => "<div>{$failingRule->message()}</div>",
                $errors,
            )) . '</div>';
        }
        
        $original = $this->session->get(Session::ORIGINAL_VALUES)[$name] ?? $default;

        return <<<HTML
<div>
    <label for="{$name}">{$label}</label>
    <input type="{$type}" name="{$name}" id="{$name}" value="{$original}" />
    {$errorHtml}
</div>
HTML;
    }
    
    // …
}