Views

Fast's view layer is refreshingly boring, and that is a feature. There is no template engine, no compilation step, and no new syntax to learn. Your templates are just PHP files, and you use plain PHP to loop, branch, and print.

Enabling views

Point Fast at a directory of templates with use_views(), then render them by name with the render() helper:

use_views(__DIR__ . '/views');

get('/', fn (): string => render('home', ['title' => 'Hi']));

The template itself is embedded PHP. The data array you pass to render() becomes local variables inside the template:

<!-- views/home.php -->
<h1><?= e($title) ?></h1>

render() returns the rendered HTML as a string, which your handler can return directly (strings become HTML responses) or embed inside a larger response.

Always escape output

You will notice e($title) in that example instead of a bare <?= $title ?>. The e() helper HTML-escapes its argument, and you should wrap every value that could contain user input in it. This is your primary defense against cross-site scripting.

<p>Welcome back, <?= e($user->name) ?>.</p>

<ul>
	<?php foreach ($items as $item): ?>
		<li><?= e($item) ?></li>
	<?php endforeach ?>
</ul>

e() is an alias for escape(); use whichever you prefer. The rule of thumb is simple: if it came from outside your code, escape it on the way out.

Escaping is especially important with Fast's live fragments, where an out-of-band update can rewrite arbitrary parts of the page. Unescaped output there is not just a bug, it is a real injection risk. When in doubt, e() it.

Organizing templates

Templates can live in subdirectories, and you reference them with a slash-separated name. A file at views/live/results.php is rendered as render('live/results'). Templates can also render other templates, which is how you build partials:

<!-- views/home.php -->
<h1><?= e($title) ?></h1>
<?= render('partials/sidebar') ?>

Nested renders inherit their parent's data

When you call render() inside a template, the nested template automatically inherits the variables of the template that rendered it. You only pass what is new or different; anything you pass explicitly overrides the inherited value.

<!-- views/home.php -->
<?= render('partials/greeting') ?>      <!-- no data passed... -->

<!-- views/partials/greeting.php -->
<p>Welcome back, <?= e($user->name) ?></p>   <!-- ...yet $user is available here -->
get('/', fn (): string => render('home', ['user' => $user]));

A top-level render() (one not nested inside another) starts from just its own data, exactly as before, so this is purely additive.

Nested partials are great for reuse, but for the "wrap this page in site chrome" pattern you want a layout, which we cover next in Layouts & Slots.