Skip to content

Templates

Namespace Keel\View. Controllers return strings for HTML (Responder answers text/html); a ViewRenderer turns a template into that string. Keel wires Twig or Latte, both optional dependencies.

final class PageController
{
    public function __construct(private readonly ViewRenderer $views) {}

    #[Get('/')]
    public function home(): string
    {
        return $this->views->render('home.twig', ['title' => 'Hello']);
    }
}

Twig

composer require twig/twig
// config/views.php
return static fn(Env $env, string $projectDir): TwigConfig => new TwigConfig(
    paths: [$projectDir . '/templates'],
    cache: $projectDir . '/var/cache/twig',   // null: no compiled cache (development)
    debug: $env->bool('APP_DEBUG'),           // also enables auto_reload
    strictVariables: true,
    globals: ['app_name' => 'My app'],
);

// config/services.php
return new ServicesConfig([TwigProvider::class]);

TwigProvider registers Twig\Environment (inject it to add extensions, filters or functions in a provider of your own) and TwigRenderer as the ViewRenderer. Twig autoescapes HTML by default.

Latte

composer require latte/latte
// config/views.php
return static fn(Env $env, string $projectDir): LatteConfig => new LatteConfig(
    directory: $projectDir . '/templates',
    tempDirectory: $projectDir . '/var/cache/latte',  // null: compile in memory per process
    autoRefresh: $env->bool('APP_DEBUG'),
);

// config/services.php
return new ServicesConfig([LatteProvider::class]);

LatteProvider registers Latte\Engine and LatteRenderer. Template names are relative to the directory. Latte escapes according to context (HTML, attributes, JavaScript) by default.

Worker mode

One engine serves every request of a worker, so compiled templates stay loaded: the first render on a worker compiles, the rest reuse. Keep template globals free of per-request data; pass the request's data as parameters.

Translations

With a Translator registered (Localization) both providers add the trans filter and function and locale(): {{ 'cart.items'|trans({count: 3}) }}.

Other engines

Implement ViewRenderer over the engine of your choice and alias it in a provider:

$definitions->set(BladeRenderer::class)->alias(ViewRenderer::class);