echo-fusion / templatemanager
Framework-independent template manager for EchoFusion: a built-in Twig/Smarty-style template language, plain PHP templates, and pluggable engines such as Twig through configuration.
Requires
- php: ~8.1.0 || ~8.2.0 || ~8.3.0
- ext-mbstring: *
- echo-fusion/contracts: ^1.6 || ^2.0
Requires (Dev)
- echo-fusion/codestandard: ^1.0
- friendsofphp/php-cs-fixer: ^3.64
- phpunit/phpunit: ^9.5
- twig/twig: ^3.0
- vimeo/psalm: ^5.26
Suggests
- twig/twig: Required to use the TwigEngine adapter (^3.0).
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-12 12:21:42 UTC
README
TemplateManager renders templates for the EchoFusion framework. It has no dependency on Symfony or Laravel: it ships its own small template language (in the spirit of Twig/Smarty), supports plain PHP templates, and lets you plug in other engines such as Twig through configuration.
It implements EchoFusion\Contracts\TemplateManager\TemplateManagerInterface from echo-fusion/contracts, so other packages can depend on the interface only.
Part of EchoFusion, a modular PHP framework built from small packages.
Installation
composer require echo-fusion/templatemanager
Requirements
- PHP 8.1, 8.2 or 8.3
- ext-mbstring
Quick start
use EchoFusion\TemplateManager\TemplateManager; $templates = TemplateManager::fromConfig([ 'paths' => [__DIR__ . '/templates'], 'globals' => ['appName' => 'My app'], ]); echo $templates->render('home', ['user' => ['name' => 'Ann']]); // templates/home.tpl
With no engines setting the built-in tpl engine and plain php engine are enabled. render() returns a string; wrap it yourself in a response, e.g. new HtmlResponse($templates->render('home')) from echo-fusion/responsemanager.
The built-in template language (.tpl)
{# a comment #} <h1>{{ title|upper }}</h1> {# output is HTML-escaped #} <p>{{ user.name }} ({{ user.email|default('no email') }})</p> <div>{{ html|raw }}</div> {# raw = not escaped #} {% set total = price * qty %} {% if total > 100 %}big{% elseif total > 10 %}medium{% else %}small{% endif %} <ul> {% for post in posts %} <li class="{{ loop.first ? 'first' : '' }}">{{ loop.index }}. {{ post.title }}</li> {% else %} <li>No posts</li> {% endfor %} </ul> {% for key, value in settings %}{{ key }}={{ value }} {% endfor %} {% include "partials/footer" %}
- Variables:
a.b.creads array keys,ArrayAccess, public properties, and the gettersgetB(),isB(),hasB(). Missing values print nothing. - Expressions: strings, numbers,
true/false/null,[lists],+ - * / %,~(concatenate),== != < > <= >= === !==,and/or/not,?:and??. Function calls are not allowed; use filters. - Loops:
loop.index,loop.index0,loop.first,loop.last,loop.length. An{% else %}runs when there is nothing to loop over. - Filters:
upper lower capitalize title trim length default join keys first last reverse date json number truncate replace url_encode raw escape. Chain them:{{ name|trim|upper }}. - Auto-escaping is on. Turn it off with the
autoescapeoption, or force it with|e.
Layouts: extends and block
{# templates/layouts/base.tpl #} <html><head><title>{% block title %}My site{% endblock %}</title></head> <body>{% block content %}{% endblock %}</body></html>
{# templates/blog/index.tpl #} {% extends "layouts/base" %} {% block title %}Blog{% endblock %} {% block content %}<h1>{{ heading }}</h1>{% endblock %}
Inheritance can be nested. In a child template only {% block %} and {% set %} may appear outside blocks.
Custom filters
use EchoFusion\TemplateManager\Engines\SimpleEngine; $engine = new SimpleEngine($templates->getFinder(), 'tpl'); $engine->addFilter('money', fn (float $v, string $cur = '$') => $cur . number_format($v, 2)); $templates->addEngine('tpl', $engine);
Compilation and cache
Templates are compiled to PHP files. Options for the tpl engine:
| Option | Default | |
|---|---|---|
cache |
<system temp>/echofusion-templates |
cache directory |
auto_reload |
true |
recompile when the template changes; set false in production for the fastest start |
autoescape |
true |
HTML-escape {{ }} |
Expressions are translated token by token, so a template cannot inject PHP code.
Plain PHP templates (.php)
<?php $this->layout('layouts/main', ['title' => 'Home']) ?> <?php $this->start('sidebar') ?><nav>...</nav><?php $this->stop() ?> <h1><?= $this->e($title) ?></h1> <?= $this->insert('partials/list', ['items' => $items]) ?>
In a layout, <?= $this->section('content') ?> is the child output and $this->section('sidebar', 'default') any named section. $this->e() escapes. Section output is already rendered, so it is not escaped again.
Namespaces (modules)
Give a directory a namespace and address its templates as @namespace/name:
$templates->addPath(__DIR__ . '/modules/blog/templates', 'blog'); $templates->render('@blog/posts/index');
This pairs with echo-fusion/modulemanager:
foreach ($modules->getTemplatePaths() as $name => $path) { $templates->addPath($path, $name); }
Directories are searched in the order they were added, so add an application directory first to override a module template.
Configuration
return [ 'paths' => [ __DIR__ . '/templates', // no key: default namespace 'blog' => __DIR__ . '/modules/blog/views', // string key: namespace ], 'globals' => ['appName' => 'My app'], 'engines' => [ // order matters: first engine that has the template wins 'tpl' => ['options' => ['cache' => __DIR__ . '/storage/templates', 'auto_reload' => false]], 'php' => [], 'twig' => ['options' => ['cache' => __DIR__ . '/storage/twig']], // needs twig/twig 'blade' => ['class' => MyBladeEngine::class, 'options' => []], // any engine class ], ];
An engine entry is the file extension. tpl, php and twig are built in; for any other extension give a class. Names can carry the extension (render('home.twig')) to pick an engine explicitly. Pass the same array to TemplateManager::fromConfig(), or to $manager->configure() on an existing manager.
Your own engine
Implement EchoFusion\Contracts\TemplateManager\TemplateEngineInterface (render(), exists()). The manager creates it as new YourEngine(TemplateFinder $finder, string $extension, array $options); use $finder->find($name, $extension) to locate files and namespaces.
Twig
TwigEngine hands the manager's directories and namespaces to Twig, so @blog/post works the same way. options go to Twig\Environment. Twig is optional: without twig/twig installed, only configuring a twig engine fails.
Service provider
EchoFusion\TemplateManager\Providers\TemplateManagerProvider registers TemplateManagerInterface and TemplateEngineInterface in a container and, on boot(), configures the manager from the templates section of your config.
Errors
All exceptions extend EchoFusion\TemplateManager\Exceptions\TemplateException: TemplateNotFoundException, TemplateSyntaxException (with template name and line), InvalidConfigException.
Template names are validated: .., absolute paths, backslashes and NUL bytes are never resolved, so a user-controlled name cannot read files outside the template directories.
Testing
composer test
Credits
License
The MIT License (MIT). Please see LICENSE for more information.