🦖
[■] API reference
codesaur/template View on GitHub

API Documentation - codesaur/template

Last Updated: 2026-09-09

Autoescape: {{ }} HTML-escapes string values by default. Use |raw where actual HTML must be output. See Autoescape.


Table of Contents


Overview

codesaur/template consists of 3 classes:

  1. MemoryTemplate - Full template engine (if, for, filter, function, macro, expression parser, 33 built-in filters)
  2. FileTemplate - File-based template loader wrapper (extends MemoryTemplate)
  3. Markup - Wrapper marking an HTML value as "safe" (exempt from autoescape)

Inheritance Hierarchy:

MemoryTemplate  (full engine)
    |-- FileTemplate  (file reader wrapper)
Markup  (safe HTML marker, Stringable)

MemoryTemplate

Full template engine. Includes if, for, macro, filter, function, and expression parser.

Class Signature

class MemoryTemplate

Properties

protected string $html

The main HTML or text source of the template.

protected array<string, mixed> $vars

Array of variables to be inserted into the template.

protected array<string, callable> $filters

Registered filters.

protected array<string, callable> $functions

Registered functions.

protected bool $autoEscape = true

Whether print node ({{ }}) results are HTML-escaped automatically.


Constructor

__construct(string $template = '', array $vars = [])

Creates a MemoryTemplate object. Automatically registers built-in filters and functions.

Parameters:

  • string $template - Initial template value (default: '')
  • array $vars - Variables array (default: [])

Example:

$template = new MemoryTemplate('Hello, {{ name }}!', ['name' => 'World']);

Variable Management

set(string $key, $value): void

Add or update a variable.

$template->set('name', 'John');

setVars(array $values): void

Add multiple variables at once.

$template->setVars(['name' => 'John', 'age' => 30]);

get(string $key): mixed

Returns variable value by reference. Returns null if not found.

$value = &$template->get('name');

getVars(): array<string, mixed>

Returns all variables as an array.

$vars = $template->getVars();

Template Source Management

source(string $html): void

Set the template source content.

$template->source('<h1>{{ title }}</h1>');

getSource(): string

Returns the template source content.

$source = $template->getSource();

Output

output(): string

Compile the template and return final HTML.

$html = $template->output();

render(): void

Compile the template and echo it.

$template->render();

__toString(): string

Returns output() when the object is echoed.

echo $template;

Filter / Function Registration

addFilter(string $name, callable $callback): void

Add a custom filter. Used in templates as {{ value|name }}.

$template->addFilter('truncate', fn($v, int $len = 100) => mb_substr((string) $v, 0, $len));
// {{ description|truncate(50) }}

addFunction(string $name, callable $callback): void

Add a custom function. Used in templates as {{ name(args) }}.

$template->addFunction('link', fn($route) => "/app/$route");
// {{ link('home') }}

Autoescape Control

setAutoEscape(bool $enabled): void

Enable or disable autoescape. Default: true.

$template->setAutoEscape(false); // for non-HTML templates (plain text, e-mail, ...)

isAutoEscape(): bool

Whether autoescape is enabled.

if ($template->isAutoEscape()) { ... }

protected escape(string $value): string

The HTML escaping strategy (htmlspecialchars, ENT_QUOTES | ENT_SUBSTITUTE, UTF-8). Used by both autoescape and the html strategy of the e/escape filter. Subclasses may override it to apply a different strategy.


Autoescape

Like Twig, {{ }} HTML-escapes by default. User-supplied values placed directly into a template cannot cause XSS:

$t = new MemoryTemplate('<p>{{ comment }}</p>', ['comment' => '<script>alert(1)</script>']);
echo $t; // <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>

Values that are NOT escaped ("safe")

ValueResultExample
Result of the |raw filterRaw{{ record.content|raw }}
Result of the |e / |escape filterNot double-escaped{{ name|e }}
Markup objectRawreturned from a custom filter/function
MemoryTemplate / FileTemplate objectRaw (rendered with its own setting){{ content }} in a layout
Macro return valueRaw (its body is already escaped){{ _self.button('OK') }}
int, float, bool, nullAs is{{ count }}

Everything else (strings, objects with __toString, string results of filters/functions) is escaped.

Losing the safe mark

The safe mark travels with the value (like Twig's Markup). Any later operation that turns a safe value into a string drops the mark, and the result is escaped again:

ExpressionResultNote
{{ v|upper|e }}escaped onceoutermost filter is e
{{ v|e|upper }}double-escapedupper produced a string, same as Twig
{{ v|raw|upper }}escapedsame
{{ v|raw ~ '!' }}escaped~ produces a string
{{ ok ? v|raw : v }}per branchraw branch is raw, the other is escaped
{% set x = v|raw %}{{ x }}rawset keeps the object

Filters that need |raw

Filters that produce HTML or HTML-significant characters return plain strings, so their output is escaped unless followed by |raw:

{{ text|nl2br|raw }}
<script>var data = {{ data|json_encode|raw }};</script>
{{ html|striptags('<b>')|raw }}

Returning HTML from custom filters/functions

Return a Markup object:

use codesaur\Template\Markup;

$template->addFilter('badge', fn($v) => new Markup('<span class="badge">' . htmlspecialchars((string) $v) . '</span>'));
// {{ status|badge }} -> <span class="badge">...</span>

Disabling

For non-HTML templates (plain text, e-mail, SQL, ...):

$template->setAutoEscape(false);

When disabled, raw is a no-op, e returns a plain string, and macros return plain strings.


Markup

final class implementing \Stringable that marks an HTML value as exempt from autoescape.

final class Markup implements \Stringable
{
    public function __construct(private readonly string $html)
    public function __toString(): string
}

The raw and e/escape filters and macro return values are wrapped in this class when autoescape is enabled. Custom filters/functions that return HTML should wrap their result in it as well.


FileTemplate

Extends MemoryTemplate to load templates from the file system. All engine logic lives in MemoryTemplate. FileTemplate only reads files and passes content to the engine.

Class Signature

class FileTemplate extends MemoryTemplate

Properties

protected string $filepath

Full path of the template file.


Methods

__construct(string $template = '', array $vars = [])

FileTemplate constructor.

Parameters:

  • string $template - Path to the template file (can be empty)
  • array $vars - Variables array (default: [])
$template = new FileTemplate(__DIR__ . '/template.html', ['name' => 'World']);

file(string $filepath): void

Set the template file path.

Throws: \InvalidArgumentException - If file name is empty

$template->file(__DIR__ . '/template.html');

getFileName(): string

Returns the template file path.

$path = $template->getFileName();

getFileSource(): string

Reads and returns the file content.

Throws: \RuntimeException - If file not found or read error

$content = $template->getFileSource();

output(): string

Reads the file and compiles it to final HTML.

Throws: \RuntimeException - If file read error

$html = $template->output();

Inherited Methods

FileTemplate inherits all MemoryTemplate public methods:

  • Variable management: set, setVars, get, getVars
  • Template source: source, getSource
  • Output: render, __toString
  • Filter/Function: addFilter, addFunction
  • Autoescape: setAutoEscape, isAutoEscape

Built-in Filters

Automatically registered in the MemoryTemplate constructor:

FilterDescriptionExample
intCast to integer{{ value|int }}
roundRound number{{ price|round(2) }}
number_formatFormat number{{ price|number_format(2, '.', ',') }}
json_encodeEncode to JSON (needs |raw){{ data|json_encode|raw }}
upperUppercase{{ name|upper }}
lowerLowercase{{ name|lower }}
capitalizeCapitalize first letter{{ name|capitalize }}
nl2brNewlines to <br> (needs |raw){{ text|nl2br|raw }}
url_encodeURL encode{{ url|url_encode }}
rawExempt from autoescape (marks value as safe){{ html|raw }}
e / escapeEscape (html default, js, url); result is safe, never double-escaped. js is safe inside '...', "..." and `...` literals{{ input|e }}, {{ s|e('js') }}
dateFormat date{{ d|date('Y-m-d') }}
lengthGet length{{ items|length }}
keysGet array keys{{ data|keys }}
firstFirst element{{ items|first }}
lastLast element{{ items|last }}
sliceExtract portion{{ text|slice(0, 5) }}
mergeMerge arrays{{ arr|merge([4, 5]) }}
splitSplit string{{ csv|split(',') }}
defaultDefault value{{ name|default('Unknown') }}
formatsprintf{{ 'Hi %s'|format(name) }}
absAbsolute value{{ num|abs }}
trimStrip whitespace{{ text|trim }}
striptagsStrip HTML tags (needs |raw if allowed tags are kept){{ html|striptags }}
titleTitle Case{{ name|title }}
joinJoin array{{ items|join(', ') }}
reverseReverse{{ items|reverse }}
sortSort array{{ items|sort }}
uniqueRemove duplicates{{ items|unique }}
columnArray column{{ users|column('name') }}
batchChunk array{{ items|batch(3) }}
valuesArray values only{{ data|values }}
replaceReplace text{{ text|replace({'a': 'b'}) }}
wordwrapWrap lines{{ text|wordwrap(80) }}
json_decodeDecode JSON{{ json|json_decode }}

Built-in Functions

FunctionDescriptionExample
attributeGet array element{{ attribute(obj, key) }}
rangeNumber sequence{{ range(1, 10) }}
maxMaximum value{{ max(a, b) }}
minMinimum value{{ min(a, b) }}

Supported Template Syntax

Output

  • {{ variable }} - Variable (strings are HTML-escaped, see Autoescape)
  • {{ html|raw }} - No escaping
  • {{ variable|filter }} - Filter chain
  • {{ function(args) }} - Function call
  • {{ a ? b : c }} - Ternary operator
  • {{ a ?? b }} - Null coalescing
  • {{ a ~ b }} - Concat operator

Control Structures

  • {% if cond %}...{% elseif cond %}...{% else %}...{% endif %}
  • {% for item in items %}...{% endfor %}
  • {% for item in items %}...{% else %}...{% endfor %} (else branch renders when items is empty / not iterable)
  • {% for key, val in items %}...{% endfor %}
  • {% set name = value %}
  • {% macro name(params) %}...{% endmacro %}

Loop Variables

Inside {% for %}, the loop object is available:

  • loop.index (starts from 1)
  • loop.index0 (starts from 0)
  • loop.first (is this the first iteration?)
  • loop.last (is this the last iteration?)
  • loop.length (total count)

Tests

  • is defined, is empty, is null, is iterable, is even, is odd
  • is not defined, is not empty, etc.

Operators

  • Comparison: ==, !=, <, >, <=, >=
  • Logic: and, or, not
  • Membership: in, not in - {% if type in ['image', 'video'] %} (arrays, strings, Traversable)
  • String: starts with, ends with, matches (regex) - {% if email matches '/^[^@]+@[^@]+$/' %}
  • Arithmetic: +, -, *, /, % and unary minus (-5, -price)

Literals

  • String: 'hello', "hello"
  • Number: 42, 3.14
  • Boolean: true, false
  • Null: null, none
  • Array: [1, 2, 3]
  • Hash: {'key': 'value'}

Access

  • Dot notation: user.name
  • Bracket notation: user['name']
  • Filter chain: value|filter1|filter2(arg)
  • Method call: user.can('edit'), auth.is('admin') (invokes public methods on objects via method_exists)
  • Callable map call: helpers.upper('hi') (invokes a callable stored as an array element)

Examples

MemoryTemplate -- full engine

use codesaur\Template\MemoryTemplate;

$t = new MemoryTemplate(
    '{% for item in items %}{{ loop.index }}. {{ item|upper }} {% endfor %}',
    ['items' => ['php', 'js', 'go']]
);
echo $t; // 1. PHP 2. JS 3. GO

// Custom function
$t = new MemoryTemplate('{{ greet("World") }}');
$t->addFunction('greet', fn($name) => "Hello, $name!");
echo $t; // Hello, World!

// Custom filter
$t = new MemoryTemplate('{{ name|reverse }}', ['name' => 'hello']);
$t->addFilter('reverse', fn($v) => strrev((string) $v));
echo $t; // olleh

// for/else - else branch renders when collection is empty
$t = new MemoryTemplate(
    '{% for item in items %}{{ item }},{% else %}empty{% endfor %}',
    ['items' => []]
);
echo $t; // empty

// Object method call from expressions
$user = new class {
    public function can(string $perm): bool { return $perm === 'edit'; }
};
$t = new MemoryTemplate(
    "{{ user.can('edit') ? 'yes' : 'no' }}|{{ user.can('admin') ? 'yes' : 'no' }}",
    ['user' => $user]
);
echo $t; // yes|no

FileTemplate

use codesaur\Template\FileTemplate;

$template = new FileTemplate(__DIR__ . '/page.html', [
    'title' => 'My Page',
    'users' => [['name' => 'John'], ['name' => 'Jane']]
]);
$template->addFunction('link', fn($route, $params = []) => '/app/' . $route);
echo $template->output();

Exception Reference

\InvalidArgumentException

  • FileTemplate::file() - If file name is empty

\RuntimeException

  • FileTemplate::getFileSource() - If file not found or read error
  • FileTemplate::output() - If file read error

Best Practices

  1. MemoryTemplate - runs the full engine, sufficient for most use cases
  2. FileTemplate - use only when loading templates from the file system
  3. Custom functions - register value-producing logic (like text(), link()) as functions
  4. Custom filters - register value-transforming logic (like |reverse, |truncate) as filters
  5. HTML comments - use <!-- comment --> in templates ({# #} is not supported)
  6. |raw - only for trusted HTML (admin-authored content, markup you generated yourself). Never apply |raw to user input
  7. Nested templates - pass the object to the layout and print {{ content }}; {{ content.output() }} double-escapes