Overriding kernel operators
Sometimes the kernel's operator is almost right: ezurl should know about other sites, upcase should follow a
house rule. Editing the kernel is not an option (an upgrade replaces the file), so an extension replaces the
operator. This page shows the one pattern that holds up, why the simpler attempts fail, and the risks that remain.
Read Registration order and overrides first for the rules behind it.
Contents
- The pattern
- A real case: ezurlmultisite
- Step by step
- Why registering one name fails
- Drop the compile-time code you change
- Order, siteaccesses and Velocity
- Risks and how to keep them small
The pattern
- Find the class that owns the name and all the names it registers (the
operator_namesof its entry in theeztemplateautoload.phpthat registers it, or itsoperatorList()). - Write a subclass in your extension. Override
modify()for the names you change and callparent::modify()for everything else. - If you change what an operator does, remove that operator's compile-time code in
operatorTemplateHints(). - Register the subclass in your extension's
autoloads/eztemplateautoload.phpfor every name of the parent class. - Make sure your extension's file is read after the one that registered the parent (normally it is: the kernel and the template library are read before every extension).
Because your class answers every name, the parent class is never loaded on that siteaccess, so it can never take a name back.
A real case: ezurlmultisite
Extension ezurlmultisite (active on a development installation) makes ezurl produce links to the right site when content belongs to
another participating siteaccess. Its autoloads/eztemplateautoload.php:
$eZTemplateOperatorArray[] = array(
'script' => 'extension/ezurlmultisite/classes/ezurlmultisiteurloperator.php',
'class' => 'eZURLMultisiteURLOperator',
'operator_names' => array( 'ezurl', 'ezroot', 'ezdesign', 'ezimage', 'exturl',
'ezsys', 'ezhttp', 'ezhttp_hasvariable', 'ezini', 'ezini_hasvariable' )
);These are exactly the ten names kernel/common/eztemplateautoload.php registers for eZURLOperator. The class,
in extension/ezurlmultisite/classes/ezurlmultisiteurloperator.php, follows the pattern (shortened):
class eZURLMultisiteURLOperator extends eZURLOperator
{
public function operatorTemplateHints()
{
$hints = parent::operatorTemplateHints();
// ezurl is decided at run time: which siteaccess a link belongs to
// depends on the siteaccess the compiled template is shown in.
if ( isset( $hints[$this->URLName] ) )
{
$hints[$this->URLName]['element-transformation'] = false;
unset( $hints[$this->URLName]['element-transformation-func'] );
}
return $hints;
}
public function modify( $tpl, $operatorName, $operatorParameters, $rootNamespace, $currentNamespace, &$operatorValue, $namedParameters, $placement )
{
if ( $operatorName === $this->URLName )
{
// ... multisite handling, then return
}
parent::modify( $tpl, $operatorName, $operatorParameters, $rootNamespace, $currentNamespace, $operatorValue, $namedParameters, $placement );
}
}Its doc comment states the reason for registering all ten names: "so the template engine never creates the kernel
object next to this one (it registers an object for all the names of its operatorList() the first time one of
them runs). No kernel override and no EZP_AUTOLOAD_ALLOW_KERNEL_OVERRIDE are needed." It also adds a parameter to
ezurl by extending namedParameterList() from the parent's list.
Step by step
The examples below override upcase, one of the fifteen names of eZTemplateStringOperator
(lib/eztemplate/classes/eztemplatestringoperator.php: upcase, downcase, count_words, count_chars, trim,
break, wrap, shorten, pad, upfirst, upword, simplify, wash, chr, ord). The override adds a
visible [override] marker so the output shows which class answered.
The subclass, extension/myextension/autoloads/c11stringoperatoroverride.php:
class C11StringOperatorOverride extends eZTemplateStringOperator
{
function operatorTemplateHints()
{
$hints = parent::operatorTemplateHints();
unset( $hints['upcase'] ); // no inline PHP for upcase: compiled templates call modify() below
return $hints;
}
function modify( $tpl, $operatorName, $operatorParameters, $rootNamespace, $currentNamespace,
&$operatorValue, $namedParameters, $placement = false )
{
if ( $operatorName === 'upcase' )
{
$operatorValue = '[override] ' . mb_strtoupper( (string)$operatorValue );
return;
}
parent::modify( $tpl, $operatorName, $operatorParameters, $rootNamespace, $currentNamespace,
$operatorValue, $namedParameters, $placement );
}
}The registration, extension/myextension/autoloads/eztemplateautoload.php:
$eZTemplateOperatorArray = array();
$eZTemplateOperatorArray[] = array(
'script' => 'extension/myextension/autoloads/c11stringoperatoroverride.php',
'class' => 'C11StringOperatorOverride',
'operator_names' => array( 'upcase', 'downcase', 'count_words', 'count_chars', 'trim', 'break', 'wrap', 'shorten',
'pad', 'upfirst', 'upword', 'simplify', 'wash', 'chr', 'ord' ) );and extension/myextension/settings/site.ini.append.php:
[TemplateSettings]
ExtensionAutoloadPath[]=myextension
*/ ?>Not run on a development installation as an installed extension: the outputs below were rendered by a script that registered the same
entry on the template engine in memory, for that process only, with the class in a scratch folder (the scratch copy
has one extra static switch that keeps the hints, used in the section on compile-time code). The template
uses upcase, then wash, then upcase again:
'fit & healthy'|upcase
'Fit & Healthy'|wash
'fit & healthy'|upcaseOutput:
[override] FIT & HEALTHY
Fit & Healthy
[override] FIT & HEALTHYOutput with the template compiler (compiled into a scratch folder):
[override] FIT & HEALTHY
Fit & Healthy
[override] FIT & HEALTHYBoth upcase calls are answered by the override, in the interpreted and in the compiled template, and wash
still works: the subclass hands it to the parent's modify().
Why registering one name fails
Registering the subclass (or any class) only for upcase looks enough, and works in a quick test:
'fit & healthy'|upcaseOutput:
[override] FIT & HEALTHYIt stops working as soon as the request uses any other operator of eZTemplateStringOperator. Loading that class
registers it for all fifteen names again, upcase included:
'fit & healthy'|upcase
'Fit & Healthy'|wash
'fit & healthy'|upcaseOutput:
[override] FIT & HEALTHY
Fit & Healthy
FIT & HEALTHYOutput with the template compiler (compiled into a scratch folder):
FIT & HEALTHY
Fit & Healthy
FIT & HEALTHYIn the compiled template not even the first line is overridden: the compiler loaded the library class for wash
while compiling and wrote the library's inline code for both upcase calls.
Drop the compile-time code you change
Many library operators declare element-transformation hints: when a template is compiled, the operator class
writes PHP for the call straight into the compiled template (for upcase, a call of mb_strtoupper()), and
modify() is never called for it again. A subclass inherits those hints. If it changes modify() but keeps the
hints, the override works in interpreted templates and is silently ignored in compiled ones. The same subclass with
the library's hints kept, registered for all fifteen names:
'fit & healthy'|upcase
'Fit & Healthy'|wash
'fit & healthy'|upcaseOutput:
[override] FIT & HEALTHY
Fit & Healthy
[override] FIT & HEALTHYOutput with the template compiler (compiled into a scratch folder):
FIT & HEALTHY
Fit & Healthy
FIT & HEALTHYThe development installation used here, like every default installation, compiles templates ([TemplateSettings] TemplateCompile=enabled in
settings/site.ini), so the compiled result is what visitors get. Remove the hint entry of each operator you change
(unset( $hints['upcase'] )), or set element-transformation to false as ezurlmultisite does. Keep the hints of
the names you only pass through to the parent.
Order, siteaccesses and Velocity
- Extensions are read after the kernel and the template library, so an extension's registration of a kernel
operator name is always the later one. Between two extensions that register the same names, the one whose
site.ini.append.phpis merged later wins: that follows[ExtensionSettings] ActiveExtensions[]and the siteaccess'sActiveAccessExtensions[]. Avoid needing that. - An override registered by an extension that is active on one siteaccess only (an access extension) applies only there. Check every siteaccess that renders the templates, including the admin when it previews content.
- Clear the INI and template caches after adding the registration (compiled templates keep the old inline code
until they are compiled again):
php bin/php/ezcache.php --clear-tag=ini,template --allow-root-user. Under Velocity the workers keep the operator registrations of their warm-up: on a development installation run./console exp:velocity deploy --allow-root-user, which clears these caches and restarts Velocity.
Risks and how to keep them small
- Upgrades: your subclass depends on the parent's internals (
$this->URLName, the hint array, themodify()signature). After a kernel upgrade, read the parent class's changes and run your templates in both interpreted and compiled mode. - New names in the parent: if a kernel release adds an operator to the parent class, your registration does
not list it, and the parent class gets loaded for that name and takes all names back. Compare your
operator_nameswith the parent'soperatorList()in a test (array_diff()of the two must be empty). - Two extensions overriding the same class: only one registration can win. Merge them into one subclass.
- Hidden changes: an override changes what every template on the siteaccess gets, including templates of other extensions. Prefer a new operator with its own name (Creating an operator, step by step) when only your own templates need the new behavior.
- No kernel class override: you do not need
EZP_AUTOLOAD_ALLOW_KERNEL_OVERRIDEor a copy of the kernel class for this; the operator registration is the supported hook.
Related pages
- Registration order and overrides
- The operator class interface
- Registering operators
- URL operators:
ezurland its siblings as ezurlmultisite serves them - The template compiler
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.