Skip to content

Template Engine

Laureano Lamonega edited this page Jul 17, 2026 · 2 revisions

Template Engine

Antimonial ships a built-in template engine inspired by Blade/Twig. Templates are plain .php files in app/Views/ that compile to cached PHP. Auto-escaping is on by default — {{ }} escapes output (XSS-safe), {{{ }}} emits raw HTML.

Configuring the view path

The view directory is not assumed. You must declare it once, typically in your front controller:

Antimonial\View\View::setViewPath(ROOT_PATH . '/app/Views');

Before v0.11.0 the framework fell back to ROOT_PATH.'/app/Views' when unset. That fallback was removed. Since v0.11.0, rendering without a configured path throws RuntimeException telling you to call View::setViewPath(). The starter skeleton already does this in public/index.php, so composer create-project works out of the box.

Directives

Echo

{{ $name }}              {{-- Escaped echo --}}
{{ $name|upper }}        {{-- With filter --}}
{{{ $html }}}            {{-- Raw (trusted) echo --}}

Conditionals

@if($count > 0)
  <p>{{ $count }} items</p>
@elseif($count === 1)
  <p>One item</p>
@else
  <p>None</p>
@endif

@unless($active)
  <p>Inactive</p>
@endunless

Loops

@foreach($users as $user)
  <li>{{ $user['name'] }}</li>
@endforeach

@for($i = 0; $i < 10; $i++)
  <span>{{ $i }}</span>
@endfor

@while($n > 0)
  {{ $n-- }}
@endwhile

Switch

@switch($status)
  @case('active')   <span class="ok">Active</span>
  @case('pending')  <span class="warn">Pending</span>
  @default          <span class="muted">{{ $status }}</span>
@endswitch

Internally rewritten to @if/@elseif/@else because PHP does not allow inline HTML inside switch alternative syntax.

Other Directives

@isset($user)      <p>{{ $user['name'] }}</p>   @endisset
@empty($cart)      <p>Cart is empty</p>          @endempty
@set($total = count($users))
@php echo time(); @endphp
@csrf                                          {{-- CSRF token field --}}
@include('partials/nav')                       {{-- Inherits parent vars --}}
@include('item', ['id' => 1])                  {{-- Explicit data --}}
@end                                            {{-- Closes most recent block --}}

Comments

{{-- This will not appear in the output --}}

Layouts (Inheritance)

{{-- users/index.php --}}
@extends('layouts/main')

@section('title')
Users
@endsection

<h1>Users</h1>   {{-- Becomes $content in layout --}}
{{-- layouts/main.php --}}
<html>
<head><title>@yield('title', 'Default')</title></head>
<body>
  <main>{{{ $content }}}</main>
</body>
</html>

Filters

Apply filters with the pipe | syntax:

{{ $name|upper }}
{{ $name|trim|length }}
{{ $date|date:Y-m-d }}

Filters also work in directive expressions:

@if($posts|length > 0)
@for($i = 0; $i < $items|count; $i++)

Built-in Filters

Filter Description
escape / e HTML-escape (default for {{ }})
raw Unescaped string
upper strtoupper()
lower strtolower()
trim trim()
length String length or count()
json Pretty-printed JSON
date Date formatting (date:Y-m-d)

Custom Filters

use Antimonial\View\Filters;

Filters::add('slug', fn ($v) => strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $v))));

Caching

Compiled templates are cached in storage/views/. The cache is automatically invalidated when the framework version changes (via Compiler::VERSION).

Clone this wiki locally