PHPackages                             maurice2k/zenplate - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. maurice2k/zenplate

ActiveLibrary[Templating &amp; Views](/categories/templating)

maurice2k/zenplate
==================

Simple and fast PHP based template engine

0.6.0(2mo ago)43782LGPL-3.0PHPPHP ^8.1CI passing

Since Nov 18Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/maurice2k/zenplate)[ Packagist](https://packagist.org/packages/maurice2k/zenplate)[ RSS](/packages/maurice2k-zenplate/feed)WikiDiscussions master Synced today

READMEChangelog (1)Dependencies (1)Versions (6)Used By (0)

Zenplate
========

[](#zenplate)

A small, fast PHP template engine — meant for things like emails, simple config files, and one-shot text rendering. Templates compile to plain PHP, which is then evaluated with the assigned variables in scope.

- Tiny surface area (two classes: `Compiler`, `Runner`)
- No dependencies beyond PHP 8.1+
- Variable substitution with deep dot/bracket access
- `if` / `elseif` / `else` / `/if` blocks
- A small whitelist of safe functions inside `if` (`strlen`, `strtoupper`, `strtolower`)
- Custom delimiters
- Compile-once / run-many for cached templates

Install
-------

[](#install)

```
composer require maurice2k/zenplate
```

Requires PHP `^8.1`.

Quick start
-----------

[](#quick-start)

```
use Maurice\Zenplate\Runner;

$runner = new Runner();
$runner->assign('name', 'Alice');

echo $runner->run('Hello {%name}!');
// => Hello Alice!
```

Assign many variables at once with an associative array:

```
$runner->assign([
    'subject' => 'Welcome',
    'user'    => ['name' => 'Alice', 'role' => 'admin'],
]);
```

Variables
---------

[](#variables)

Variables start with `%`. They're wrapped in the configured delimiters (default `{` / `}`).

### Simple

[](#simple)

```
$runner->assign('name', 'world');
$runner->run('Hello {%name}!');
// => Hello world!
```

### Dot access (nested arrays / objects)

[](#dot-access-nested-arrays--objects)

```
$runner->assign('user', ['name' => 'Bob', 'role' => 'admin']);
$runner->run('{%user.name} ({%user.role})');
// => Bob (admin)
```

Dot access nests arbitrarily deep — missing keys at any level render as the empty string, no warnings:

```
$runner->assign('cfg', ['feature' => ['enabled' => true]]);
$runner->run('{%cfg.feature.enabled}');         // => 1
$runner->run('{%cfg.feature.does.not.exist}');  // => (empty)
```

### Bracket access

[](#bracket-access)

```
$runner->assign('items', ['first', 'second', 'third']);
$runner->run('{%items[1]}');               // => second

$runner->assign('data', ['foo' => 'bar']);
$runner->run('{%data["foo"]}');            // => bar
```

### Mixed dot + bracket

[](#mixed-dot--bracket)

```
$runner->assign('users', [
    'admins' => [
        ['name' => 'Alice'],
        ['name' => 'Bob'],
    ],
]);
$runner->run('{%users.admins[1].name}');   // => Bob
```

### Empty / falsy values

[](#empty--falsy-values)

Variables are rendered through PHP's `empty()`. That means **all of these render as the empty string**:

ValueOutput`null``''``''``''``0` (int)`''``'0'` (str)`''``false``''``[]``''`This is intentional historical behavior — handy for templates that print a default for "missing-ish" values, surprising if you actually wanted to print `0`. If you need `0` to render, pass `'0 '` or convert upstream.

Conditionals
------------

[](#conditionals)

```
$runner->assign('status', 'active');

$runner->run('{if %status == "active"}on{else}off{/if}');
// => on
```

`elseif` chains and `else` clauses work as expected:

```
$tpl = '{if %n == 1}one{elseif %n == 2}two{elseif %n == 3}three{else}other{/if}';
$runner->assign('n', 2);
$runner->run($tpl); // => two
```

`else` is optional:

```
$runner->run('{if %show}visible{/if}');
```

### Operators

[](#operators)

Allowed inside `{if ...}`:

- Comparison: `==`, `===`, `!=`, `!==`, ``, ``, `=`
- Logical: `&&`, `||`, unary `!`
- Arithmetic / bitwise: `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, ``
- Grouping: `( ... )`

```
$runner->assign('a', true);
$runner->assign('b', false);
$runner->run('{if (%a && !%b) || %b}hit{else}miss{/if}'); // => hit
```

A single `=` in a comparison context is treated as `==` but reported as an error (so the template still compiles to something sensible while the typo is surfaced via `Compiler::getErrors()`).

### Functions inside `if`

[](#functions-inside-if)

Only an explicit whitelist is callable from inside an `if` condition:

- `strlen`
- `strtoupper`
- `strtolower`

```
$runner->assign('s', 'hello');
$runner->run('{if strlen(%s) > 3}long{else}short{/if}'); // => long
```

Extend the list per Compiler instance:

```
use Maurice\Zenplate\Compiler;

$c = new Compiler();
$c->supportedIfFuncs[] = 'count';
```

Note: `Runner::run()` constructs its own internal `Compiler`, so to use custom functions or delimiters with the high-level API you'll currently need to compile separately and use `Runner::runCompiled()` (see below).

Custom delimiters
-----------------

[](#custom-delimiters)

```
use Maurice\Zenplate\Compiler;

$c = new Compiler();
$c->leftDelimiter  = '>';

$compiled = $c->compile('Hello !');
```

Compile once, run many
----------------------

[](#compile-once-run-many)

Compilation is regex-driven and not free; if you render the same template repeatedly, compile it once and feed the cached PHP into `runCompiled()`:

```
use Maurice\Zenplate\Compiler;
use Maurice\Zenplate\Runner;

$compiled = (new Compiler())->compile('Hello {%name}!');
file_put_contents('/var/cache/greeting.php', $compiled);

// Later, possibly in another request:
$runner = new Runner();
$runner->assign('name', 'Alice');
echo $runner->runCompiled(file_get_contents('/var/cache/greeting.php'));
```

`runCompiled()` rejects input that doesn't carry the Zenplate header so a random PHP file can't be smuggled through.

Error handling
--------------

[](#error-handling)

`Runner::run()` throws `ExecuteException` on either compile errors or runtime PHP errors:

```
use Maurice\Zenplate\Exception\ExecuteException;

try {
    $runner->run('{if %x == }nope{/if}');
} catch (ExecuteException $e) {
    // "Error compiling template; error messages: ..."
}
```

For finer-grained inspection, drive the compiler directly:

```
$c = new Compiler();
if ($c->compile($tpl) === false) {
    foreach ($c->getErrors() as $err) {
        // $err = ['type' => int, 'offset' => int, 'additional' => string, 'message' => string]
    }
}
```

`Compiler::ERROR_*` constants identify each error kind (`ERROR_IF_NO_ENDIF`, `ERROR_IF_SINGLE_EQUAL_SIGN`, etc.).

Safety notes
------------

[](#safety-notes)

The compiler emits PHP source that gets `eval()`ed by `Runner::run()`, so injection prevention is critical. Current defenses:

- **Literal `
