sailantis / clarity-engine
A fast and powerful Template engine for PHP inspired by Twig.
Requires
- php: >=8.1
- ext-mbstring: *
Requires (Dev)
- phpdocumentor/reflection-docblock: ^6.0
- phpunit/phpunit: ^9.6
Suggests
- ext-intl: Locale-aware formatting via IntlFormatModule (format_number, format_currency, format_date, ...)
Provides
None
Conflicts
None
Replaces
None
README
A fast, secure, and expressive PHP template engine – Clarity compiles
.clarity.htmltemplates into cached PHP classes for maximum performance while maintaining a sandboxed, secure execution environment.
Features
- Compiled & Cached – Templates compile to PHP classes and leverage OPcache for blazing-fast rendering
- Secure Sandbox – No arbitrary PHP execution by default; templates are strictly sandboxed with controlled access
- Opt-In Open Mode – Disable the sandbox to give templates the full power of PHP (any function call or filter, method calls,
{% php %}blocks) — Blade / Stempler / Plates parity, for trusted authors - Expressive Syntax – Clean, readable template syntax inspired by modern template engines
- Twig-Style Tests –
in,is defined,starts with,matches,divisible by, and more, with absence-tolerantdefined/null/empty - Whitespace Control –
{%- … -%}trims whitespace around a tag - Loop Fallbacks –
{% for %} … {% else %} … {% endfor %}renders theelsebranch when the sequence is empty - Template Inheritance – Reusable layouts with
extendsandblocksfor DRY template architecture - Macros – Define reusable template fragments with parameters and call them inline
- Extensible — Custom filters, functions, inline filters, block directives, and loader plugins
- Modules – Bundle filters, functions, and directives into self-registering plug-ins
- Auto-escaping – Built-in XSS protection with context-aware automatic HTML/JS/CSS escaping
- Unicode Support – Full multibyte string handling with transparent normalization
- Zero Dependencies – Standalone engine with no external dependencies beyond PHP 8.1+
Installation
composer require sailantis/clarity-engine
Requirements: PHP 8.1 or higher
Quick Start
Basic Setup
<?php require_once 'vendor/autoload.php'; use Clarity\ClarityEngine; // Initialize the engine $engine = new ClarityEngine([ 'viewPath' => __DIR__ . '/templates', 'namespaces' => [ 'admin' => __DIR__ . '/templates/admin', 'emails' => __DIR__ . '/templates/emails', ], ]); // Render a template echo $engine->render('welcome', [ 'title' => 'Welcome to Clarity', 'user' => ['name' => 'Developer'], ]);
Your First Template
templates/welcome.clarity.html:
<!DOCTYPE html> <html> <head> <title>{{ title }}</title> </head> <body> <h1>Hello, {{ user:name }}!</h1> <p>The current time is {{ "now" |> date("H:i:s") }}</p> </body> </html>
That's it! Clarity automatically compiles and caches your template.
Documentation
For Template Authors
Start here if you're writing templates:
- Getting Started – Installation, setup, and your first template
- Template Syntax – Variables, directives, operators, and control flow
- Filters & Functions – Transform data with built-in and custom filters
- Layout Inheritance – Reusable layouts with extends and blocks
For Developers
Integration and advanced topics:
- Advanced Topics – Namespaces, caching, auto-escaping, and Unicode
- Best Practices – Organization, security, performance, and testing
- Troubleshooting – Common errors and debugging techniques
Reference
- API Documentation – Auto-generated API reference for all classes
- Examples – Runnable template examples demonstrating features
- Guide Index – Complete documentation index
Output & Variables
{{ expression }} {# Output with auto-escaping #}
{{ expression | raw }} {# Output raw HTML (no escaping) #}
{{ expression |> raw }} {# Same — both | and |> are filter pipes #}
{{ user:name }} {# Array key #}
{{ user.name }} {# Object property #}
{{ items[0] }} {# Array index #}
{{ user[var] }} {# Dynamic array key #}
{{ user{var} }} {# Dynamic property #}
{{ firstName ~ ' ' ~ lastName }} {# String concatenation #}
Control Flow
{% if condition %}...{% elseif other %}...{% else %}...{% endif %}
{% for item in items %}
{{ item:name }}
{% endfor %}
{% for key, value in assocArray %} {# Loop with key variable #}
{{ key }}: {{ value }}
{% endfor %}
{% for i in 1..10 %}{{ i }}{% endfor %} {# Range: 1 to 10 (inclusive) #}
{% for i in 1...10 %}{{ i }}{% endfor %} {# Range: 1 to 9 (exclusive) #}
{% for i in 0..100 step 10 %}{{ i }}{% endfor %} {# With step #}
{% set total = items | length %}
Macros
{% macro @card(title, body) %}
<div class="card"><h3>{{ title }}</h3><p>{{ body }}</p></div>
{% endmacro %}
{% @card("Welcome", intro) %}
{% @card(article.title, article.excerpt) %}
Filters
{{ text | upper }}
{{ text |> upper }} {# both | and |> are equivalent #}
{{ price | number(2) }}
{{ timestamp | date('Y-m-d H:i') }}
{{ "Hello, %s!" | sprintf(user.name) }}
{{ tags | join(', ') }}
{{ users | map(u => u.name) | join(', ') }} {# Lambda expression #}
{{ items | filter(i => i.active) | length }}
{{ title | slug }} {# URL-friendly slug #}
{{ html | striptags }} {# Strip HTML tags #}
Common filters: upper, lower, trim, length, number, date, sprintf, json, join, split, slug, map, filter, reduce, default, empty, striptags, escape, raw
See all filters and detailed syntax →
Template Inheritance
{# layouts/base.clarity.html #} <!DOCTYPE html> <html> <head> <title>{% block title %}Default Title{% endblock %}</title> </head> <body> {% block content %}{% endblock %} </body> </html> {# pages/home.clarity.html #} {% extends "layouts/base" %} {% block title %}Home Page{% endblock %} {% block content %} <h1>Welcome!</h1> {% endblock %}
Includes
{% include "partials/header" %} {# Static include #}
{{ include("widgets/card", { title: "Hi" }) }} {# Dynamic include with context #}
Configuration
Configure the engine with these methods:
// Initialize with config array $engine = new ClarityEngine([ 'viewPath' => __DIR__ . '/templates', 'cachePath' => __DIR__ . '/cache', ]); // Or configure via setters $engine = ClarityEngine::create() ->setViewPath(__DIR__ . '/templates') ->setCachePath(__DIR__ . '/cache'); // Default: sys temp + /clarity_cache // Additional configuration $engine->setExtension('.tpl.html'); // Default: .clarity.html // Register named namespaces (convenience method) $engine->addNamespace('admin', __DIR__ . '/templates/admin'); $engine->addNamespace('emails', __DIR__ . '/templates/emails'); // Templates with a namespace prefix: {% include "admin::sidebar" %} // Unprefixed templates still resolve via the base viewPath. // Namespaces can also be passed in the constructor: // new ClarityEngine(['namespaces' => ['admin' => __DIR__ . '/templates/admin']]); // For advanced multi-source setups, set a loader directly: $engine->setLoader(new \Clarity\Template\DomainRouterLoader( ['admin' => new \Clarity\Template\FileLoader('/path/to/admin/templates')], fallback: new \Clarity\Template\FileLoader('/path/to/templates'), )); $engine->setDebugMode(true); // Runtime safety checks (dev only) // Add custom filter $engine->addFilter('currency', fn($v) => '€ ' . number_format($v, 2)); // Clear compiled templates $engine->flushCache(); // Modules: bundle filters, functions, and directives $engine->use(new \Clarity\Localization\IntlFormatModule(['locale' => 'en_US'])); $engine->use(new \Clarity\Localization\TranslationModule([ 'locale' => 'en_US', 'translations_path' => __DIR__ . '/locales', ]));
Security
Clarity is sandboxed by default:
- No arbitrary PHP execution – Templates cannot call PHP functions or access global state
- Auto-escaping by default – All output is HTML-escaped to prevent XSS attacks
- Compile-time validation – Syntax errors caught during compilation, not at runtime
- Object safety – Objects stay objects:
a.breads a public property anda:breads an array key, so method calls are unreachable from templates and PHP visibility rules apply. Container operations read an object's public properties, and thedatefilter acceptsDateTimeInterfacedirectly - Controlled lambdas – Lambda expressions can only use registered filters
PHP Mode
The sandbox can be disabled deliberately ("PHP mode"), granting templates the full power of
PHP (any function call or filter, $obj->method(), and raw PHP through either
{% php %}…{% endphp %} or the standalone {% php CODE %} form):
$engine->setSandboxMode(false);
PHP mode is intended for templates written by trusted authors. It is
equivalent to executing arbitrary PHP and disables every guarantee listed
above. Templates record which mode compiled them and are recompiled
automatically when the setting changes. Nothing is blocked by default — PHP mode is the security decision; if you want extra guardrails, add them with
setDeniedFunctions([...]).
Performance
Clarity is designed for speed. Templates compile to native PHP classes and leverage OPcache for optimal performance:
- Compiled templates – One-time compilation to PHP, then served from OPcache
- Auto-invalidation – Cache automatically refreshed when templates change
- Zero runtime overhead – Inheritance resolved at compile time
- Minimal memory footprint – Efficient compilation with predictable memory usage
Benchmark Results
Clarity is measured against the other mainstream PHP template engines rendering the same page, on the same machine and PHP build. The two charts below are generated from the benchmark.
The dot is the median render and the caps bound the fastest observation and p95, so an engine that is usually fast but occasionally slow looks different from one that is uniformly slower.
Measured in a fresh process per engine, so no engine inherits another's footprint. The bar runs from the floor that engine costs to have loaded (left cap) to the peak it reached (right cap), and the dot is what it still holds once the run is done — the figure a serving process actually carries.
The full comparison — every shape, every chart, and the environment and engine versions this run recorded — is in the benchmark report, with the public version of the same data at https://sailantis.github.io/azera-competition/benchmarks/view-engine.html.
Performance optimization guide →
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Running Tests
composer install
composer test
Or run PHPUnit directly:
php vendor/bin/phpunit
License
This project is licensed under the MIT License - see the LICENSE file for details.
Links
- Documentation – Complete guide index
- Examples – Runnable example templates
- API Reference – Auto-generated API documentation
- GitHub Issues – Report bugs or request features
Built with ❤️ for developers who value security and performance