Views & Templates
The framework uses a lightweight template engine. Templates are plain .html files — no PHP execution inside templates.
Template language reference
| Syntax | Description |
|---|---|
{{var}} |
HTML-escaped variable output. |
{{obj->prop}} |
Access object property (HTML-escaped). |
{{arr.key}} |
Access array key (HTML-escaped). |
{{{var}}} |
Raw (unescaped) HTML — use only for already-sanitized content. |
{{#if cond:}} ... {{#endif cond:}} |
Conditional block. cond is a model key. |
{{#for item in items:}} ... {{#endfor items:}} |
Loop over an array. |
{{#layout name:}} |
Wrap this template in a layout file. |
{{content}} |
Inside a layout: marks where the child template is injected. |
{{i18nKey}} |
Resolved from {locale}.json in i18nBasePath. |
Variable output
<!-- HTML-escaped (safe for all user-supplied values) -->
<p>Hello, {{user->name}}!</p>
<!-- Raw HTML (only for sanitized/trusted content) -->
<div>{{{article->htmlBody}}}</div>
Conditionals
{{#if isAdmin:}}
<a href="/admin">Admin panel</a>
{{#endif isAdmin:}}
The condition key is resolved from the model/data passed to the view. A truthy value (non-empty string, non-zero number, true) shows the block.
Loops
<ul>
{{#for article in articles:}}
<li>
<a href="/articles/{{article->id}}">{{article->title}}</a>
</li>
{{#endfor articles:}}
</ul>
Layouts
layout.html:
<!DOCTYPE html>
<html>
<head>
<title>{{pageTitle}}</title>
<link rel="stylesheet" href="/{{mainCssBundler}}">
</head>
<body>
<nav><!-- shared nav --></nav>
<main>
{{content}}
</main>
<script src="/{{mainJsBundler}}"></script>
</body>
</html>
Child template Views/Article/show.html:
{{#layout layout:}}
<article>
<h1>{{article->title}}</h1>
<p>{{article->body}}</p>
</article>
Internationalization
<button>{{save_button_label}}</button>
assets/i18n/en.json:
{
"save_button_label": "Save changes"
}
assets/i18n/es.json:
{
"save_button_label": "Guardar cambios"
}
The locale is detected automatically from the request and the correct translation file is loaded.
Passing data to views
From a controller action:
return $this->view(model: $article, data: [
'pageTitle' => $article->title,
'breadcrumbs' => [['Home', '/'], ['Articles', '/articles']],
]);
model— available as{{model->...}}in the template.data— merged into the top-level template context; keys are available directly as{{pageTitle}}.
Security
All {{var}} output is HTML-escaped via htmlspecialchars(). Only {{{var}}} skips escaping. Treat {{{...}}} as you would echo $htmlString — only use it for content you control or have already sanitized.