pbFenom
Templates are rendered by pbFenom — our fork of Fenom, shipped as the Composer package pageblocks/fenom. It is a required dependency, not an option: View type-hints \pbFenom and builds the engine through \pbFenom::factory().
"require": {
"pageblocks/fenom": "^1.0"
}The template language itself is unchanged. Everything you know about Fenom syntax — {if}, {foreach}, modifiers, {extends}, {include} — works the same way. What changed is underneath.
It is not a drop-in for upstream
The fork lives in its own namespace: the class is pbFenom, the interfaces are pbFenom\ProviderInterface and friends. You cannot swap in fenom/fenom and you cannot install both and expect them to be the same engine.
That isolation is the point — the component pins behaviour it depends on instead of tracking a moving upstream.
Why the fork exists
eval() is gone
Upstream rendered through eval() in several modes — FORCE_COMPILE even wrote the compiled file to disk and then ignored it. The fork includes the artifact it just wrote, so opcache can keep the opcodes, and the two modes that have no file by design render through a private stream wrapper instead.
Practical effect: a runtime error names the template instead of reporting eval()'d code on line N.
Modifiers accept null again
Upstream 3.0.0 added parameter types to the modifiers, which turned an ordinary null template variable into a fatal:
{$x} {* fine - htmlspecialchars(null) only warns *}
{$x|escape} {* upstream 3.0.0: fatal *}In a CMS a null field is normal, not exceptional. escape, unescape, truncate, strip, replace, ereplace, match, ematch and date treat null as "", the way they did before 3.0.0.
Recursion is bounded
A cyclic {include} used to recurse until PHP exhausted the call stack, and a cyclic {insert} until it exhausted memory. Both are fatal errors, so they are uncatchable: the visitor got a blank 500 with nothing in the log worth reading.
Now both raise an ordinary exception in milliseconds and name the template. The limit is pbFenom::$max_template_depth (32). Cost on the hot path: 1.8% on a loop of 1000 {include}s.
Nested render errors are also no longer re-wrapped once per level — messages used to read "unhandled exception in a: unhandled exception in b: ...".
addFunction() keeps its contract
The callback receives ($params, $tpl, $var), as it always has. If you want the callable's own signature to be the template API, register it with addFunctionSmart() — which, in the fork, finally accepts closures, array callables and invokable objects rather than strings only.
A missing required argument to a smart function is now reported at compile time ("Function excerpt requires the 'text' argument") instead of blowing up mid-render.
Cache signature actually covers the configuration
Registering functions did not invalidate the compile-cache signature, so two engine instances differing only in a registered function could serve each other's compiled artifacts. The signature now covers that, plus a CACHE_FORMAT constant so artifacts from an older code generator are not reused.
Strict types
All 17 source files declare strict_types=1, which surfaced four latent bugs that weak mode had been papering over — among them getProvider() receiving a bool on every template load, because strstr() returns false rather than null when a template name carries no schema.
Compiled artifacts deliberately do not declare strict types: template data is arbitrary, and coercing an int handed to a string-typed modifier is documented behaviour.
Removed
auto_trim / pbFenom::AUTO_TRIM — reserved and inert since 2013. Passing it now throws Undefined option 'auto_trim' instead of being silently accepted and ignored. {autotrim} and the :trim / :ltrim / :rtrim tag options were documented for a decade and never implemented; their page is gone.
Version
pbFenom::VERSION is 3.1, tracking the upstream generation the fork branched from. The package version is separate (pageblocks/fenom 1.x).
Where it is configured
PageBlocks builds the engine in View::init(): cache directory, auto_reload from the pageblocks_fenom_auto_reload setting, and the template providers — see View.