API Documentation - codesaur/template
Last Updated: 2026-09-09
Autoescape:
{{ }}HTML-escapes string values by default. Use|rawwhere actual HTML must be output. See Autoescape.
Table of Contents
- Overview
- MemoryTemplate
- Autoescape
- Markup
- FileTemplate
- Built-in Filters
- Supported Template Syntax
- Examples
- Exception Reference
Overview
codesaur/template consists of 3 classes:
- MemoryTemplate - Full template engine (if, for, filter, function, macro, expression parser, 33 built-in filters)
- FileTemplate - File-based template loader wrapper (extends MemoryTemplate)
- 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><script>alert(1)</script></p>
Values that are NOT escaped ("safe")
| Value | Result | Example |
|---|---|---|
Result of the |raw filter | Raw | {{ record.content|raw }} |
Result of the |e / |escape filter | Not double-escaped | {{ name|e }} |
Markup object | Raw | returned from a custom filter/function |
MemoryTemplate / FileTemplate object | Raw (rendered with its own setting) | {{ content }} in a layout |
| Macro return value | Raw (its body is already escaped) | {{ _self.button('OK') }} |
int, float, bool, null | As 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:
| Expression | Result | Note |
|---|---|---|
{{ v|upper|e }} | escaped once | outermost filter is e |
{{ v|e|upper }} | double-escaped | upper produced a string, same as Twig |
{{ v|raw|upper }} | escaped | same |
{{ v|raw ~ '!' }} | escaped | ~ produces a string |
{{ ok ? v|raw : v }} | per branch | raw branch is raw, the other is escaped |
{% set x = v|raw %}{{ x }} | raw | set 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:
| Filter | Description | Example |
|---|---|---|
int | Cast to integer | {{ value|int }} |
round | Round number | {{ price|round(2) }} |
number_format | Format number | {{ price|number_format(2, '.', ',') }} |
json_encode | Encode to JSON (needs |raw) | {{ data|json_encode|raw }} |
upper | Uppercase | {{ name|upper }} |
lower | Lowercase | {{ name|lower }} |
capitalize | Capitalize first letter | {{ name|capitalize }} |
nl2br | Newlines to <br> (needs |raw) | {{ text|nl2br|raw }} |
url_encode | URL encode | {{ url|url_encode }} |
raw | Exempt from autoescape (marks value as safe) | {{ html|raw }} |
e / escape | Escape (html default, js, url); result is safe, never double-escaped. js is safe inside '...', "..." and `...` literals | {{ input|e }}, {{ s|e('js') }} |
date | Format date | {{ d|date('Y-m-d') }} |
length | Get length | {{ items|length }} |
keys | Get array keys | {{ data|keys }} |
first | First element | {{ items|first }} |
last | Last element | {{ items|last }} |
slice | Extract portion | {{ text|slice(0, 5) }} |
merge | Merge arrays | {{ arr|merge([4, 5]) }} |
split | Split string | {{ csv|split(',') }} |
default | Default value | {{ name|default('Unknown') }} |
format | sprintf | {{ 'Hi %s'|format(name) }} |
abs | Absolute value | {{ num|abs }} |
trim | Strip whitespace | {{ text|trim }} |
striptags | Strip HTML tags (needs |raw if allowed tags are kept) | {{ html|striptags }} |
title | Title Case | {{ name|title }} |
join | Join array | {{ items|join(', ') }} |
reverse | Reverse | {{ items|reverse }} |
sort | Sort array | {{ items|sort }} |
unique | Remove duplicates | {{ items|unique }} |
column | Array column | {{ users|column('name') }} |
batch | Chunk array | {{ items|batch(3) }} |
values | Array values only | {{ data|values }} |
replace | Replace text | {{ text|replace({'a': 'b'}) }} |
wordwrap | Wrap lines | {{ text|wordwrap(80) }} |
json_decode | Decode JSON | {{ json|json_decode }} |
Built-in Functions
| Function | Description | Example |
|---|---|---|
attribute | Get array element | {{ attribute(obj, key) }} |
range | Number sequence | {{ range(1, 10) }} |
max | Maximum value | {{ max(a, b) }} |
min | Minimum 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 oddis 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 viamethod_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 errorFileTemplate::output()- If file read error
Best Practices
- MemoryTemplate - runs the full engine, sufficient for most use cases
- FileTemplate - use only when loading templates from the file system
- Custom functions - register value-producing logic (like
text(),link()) as functions - Custom filters - register value-transforming logic (like
|reverse,|truncate) as filters - HTML comments - use
<!-- comment -->in templates ({# #}is not supported) |raw- only for trusted HTML (admin-authored content, markup you generated yourself). Never apply|rawto user input- Nested templates - pass the object to the layout and print
{{ content }};{{ content.output() }}double-escapes