simsoft / twig
A lightweight Twig wrapper that simplifies template engine setup with configuration-driven initialization, namespace support, and easy extension authoring.
Requires
- php: ^8.2
- twig/twig: ^3.27
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^2
- phpunit/phpunit: ^11
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-09 01:35:46 UTC
README
A lightweight PHP wrapper for the Twig 3.x template engine. Simplifies setup with configuration-driven initialization, namespace support, and easy extension authoring.
Features
- Configuration-based initialization (paths, caching, debug, charset, timezone)
- Optional tag-aware HTML minification (what it guarantees)
- Template namespaces for organized directory structures
- Simplified extension base class with helper methods for filters, functions, and tests
- Fluent API for configuring the engine before the first render
- Auto-escaping enabled by default (XSS protection)
Requirements
- PHP 8.2+
- Twig 3.27+
- Composer
Twig releases below 3.27.0 are affected by published security advisories and
are not supported. The test suite runs on PHP 8.2 through 8.5 against both the
lowest supported and the newest Twig release, and treats any deprecation raised
from src/ as a build failure, so upcoming removals surface here before they
reach you.
Twig 4.x is not supported yet. CI tracks it in an advisory job so that breaking changes are known ahead of the 4.0 release.
Installation
composer require simsoft/twig
Quick Start
use Simsoft\Twig\Twig; $twig = new Twig([ 'path' => __DIR__ . '/templates', 'cache' => __DIR__ . '/cache', ]); // Render to string $html = $twig->render('hello', ['name' => 'World']); // Output directly $twig->display('hello', ['name' => 'World']);
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
path |
string|string[] | — | Required. Path(s) to templates directory |
fileExtension |
string | .twig |
Template file extension |
debug |
bool | false |
Enable debug mode |
charset |
string | UTF-8 |
Template charset |
cache |
string | — | Compiled template cache directory |
timezone |
string | — | Timezone for date formatting |
extensions |
array | [] |
Array of ExtensionInterface instances |
namespaces |
array | [] |
Map of namespace name → template path |
minify |
bool | false |
Minify HTML output (details) |
Unrecognized config keys will throw an InvalidArgumentException to catch typos
early. Note that key names are case-sensitive (charset, not Charset).
path is required — omitting it, or passing an empty string or array, throws an
InvalidArgumentException.
Typed Configuration (Alternative)
For IDE autocompletion, use the TwigConfig object instead of an array:
use Simsoft\Twig\Twig; use Simsoft\Twig\TwigConfig; $twig = new Twig(new TwigConfig( path: __DIR__ . '/templates', cache: __DIR__ . '/cache', debug: true, timezone: 'Asia/Kuala_Lumpur', minify: true, extensions: [new \App\MyExtension()], namespaces: [ 'layouts' => __DIR__ . '/templates/layouts', ], ));
Full Configuration Example
use Simsoft\Twig\Twig; $twig = new Twig([ 'path' => __DIR__ . '/templates', 'fileExtension' => '.twig', 'debug' => true, 'charset' => 'UTF-8', 'cache' => __DIR__ . '/cache', 'timezone' => 'Asia/Kuala_Lumpur', 'minify' => true, 'extensions' => [new \App\MyExtension()], 'namespaces' => [ 'layouts' => __DIR__ . '/templates/layouts', 'components' => __DIR__ . '/templates/components', 'macros' => __DIR__ . '/templates/macros', ], ]);
Template Namespaces
Namespaces let you reference templates from different directories:
// Renders @layouts/base.twig $twig->render('@layouts/base', ['title' => 'Home']);
HTML Minification
Enable minify to automatically strip HTML comments, collapse whitespace
between tags, and reduce output size across all rendering methods (render(),
display(), renderBlock(), renderIf()):
$twig = new Twig([ 'path' => __DIR__ . '/templates', 'minify' => true, // All output is minified ]); // This output will be minified automatically $html = $twig->render('page', ['title' => 'Home']);
The static helper Twig::minify() is also available for one-off use on any HTML
string:
$minified = Twig::minify($rawHtml);
What minification guarantees
Minification is tag-aware — the document is tokenized before any whitespace is touched, so the following hold:
- Raw-text elements are preserved byte for byte. Content inside
<pre>,<textarea>,<script>, and<style>is never altered, including indentation and blank lines. - JavaScript and CSS are safe. A string such as
var x = "<!-- hi -->";or an expression likea --> bpasses through untouched. - Attribute values are never rewritten, including values containing
>,<, quotes, or newlines. - Word spacing is preserved. Whitespace between inline elements collapses
to a single space rather than being removed, so
</span>\n<span>becomes</span> <span>and words stay separated. Whitespace around block-level elements is removed entirely, since it has no rendered effect. - Conditional comments are preserved regardless of casing (
<!--[if IE]>and<!--[If IE]>both survive). All other comments are removed. - Malformed input is safe. Unclosed tags, unterminated comments, and invalid UTF-8 are passed through rather than dropped.
Minification is idempotent — minifying already-minified output is a no-op.
Runtime API
Important
All registration must happen before the first render. Twig locks its
extension set once the environment is initialized, so calling share(),
addFilter(), addFunction(), addTest(), or addExtension() after any
render(), display(), renderBlock(), or renderIf() call throws a
LogicException.
// Share global variables $twig->share('site_name', 'My Site'); $twig->share(['app' => 'MyApp', 'version' => '1.0']); // Add filters $twig->addFilter('slug', fn (string $s) => strtolower(str_replace(' ', '-', $s))); // Add functions $twig->addFunction('asset', fn (string $path) => "/assets/{$path}"); // Add tests $twig->addTest('even', fn (int $n) => $n % 2 === 0); // Check if a template exists if ($twig->exists('email/welcome')) { $twig->display('email/welcome', $data); } // Render only if template exists (returns empty string otherwise) $sidebar = $twig->renderIf('partials/sidebar', ['items' => $menuItems]); // Render a specific block $header = $twig->renderBlock('page', 'header', ['title' => 'Welcome']); // Access underlying Twig Environment $env = $twig->getInstance();
Building Extensions
Extend Simsoft\Twig\Extension and register filters, functions, and tests in
the init() method:
<?php declare(strict_types=1); namespace App; use Simsoft\Twig\Extension; class MyExtension extends Extension { public function getGlobals(): array { return [ 'app_name' => 'My Application', ]; } protected function init(): void { $this->addFilter('obj_to_array', fn (object $obj) => (array) $obj); $this->addFunction('dump', fn (...$args) => call_user_func_array('var_dump', $args)); $this->addTest('red', function ($value) { return ($value->color ?? $value->paint ?? null) === 'red'; }); } }
For advanced extension features, see Extending Twig.
Template Authoring
See Twig for Template Designers for template syntax reference.
Why Simsoft Twig?
| simsoft/twig | slim/twig-view | rcrowe/twigbridge | twig/twig (raw) | |
|---|---|---|---|---|
| Purpose | Framework-agnostic wrapper | Slim 4 integration | Laravel integration | Core engine |
| Framework coupling | None | Slim (PSR-7/15) | Laravel | None |
| Setup | Single constructor call | DI container + middleware | ServiceProvider + config | Manual loader + environment |
| Config | Array or typed DTO | Constructor params | Laravel config file | Manual PHP code |
| Extension authoring | Base class with init() |
Use raw Twig | Laravel-specific helpers | Extend AbstractExtension |
| Namespace support | Built-in via config | Manual | Via config | Manual addPath() |
| Convenience methods | exists(), renderIf(), share(), minify |
No | No | getLoader()->exists() only |
| Config validation | Throws on typos | No | No | No |
Use simsoft/twig when you want Twig in any PHP project (custom frameworks, legacy apps, microservices, CLI tools) without framework lock-in or manual wiring.
Use something else when you're already in Laravel (rcrowe/twigbridge) or
Slim (slim/twig-view), or need custom loaders like database/S3 (use
twig/twig directly).
License
MIT — see LICENSE for details.