Fetch functions: how they work
fetch( module, function, hash( ... ) ) is how a template reads data: content, users, sections, your own tables.
This page follows a call through the code and shows how an extension adds or changes fetch functions. The
parameters of every fetch function of this installation are listed in the
fetch function reference; a first fetch function of your own is
written step by step in Writing your own fetch function.
Contents
- The path of a call
- function_definition.php
- The function collection
- When something is wrong
- Where modules are found
- Adding a fetch module in an extension
- Extending a kernel module's fetch functions
- Replacing them, and why not to
- Compiled templates
The path of a call
{def $node = fetch( 'content', 'node', hash( 'node_id', 89 ) )
$count = fetch( 'content', 'list_count', hash( 'parent_node_id', 89 ) )}
$node.name|wash: $count childrenOutput:
Fit & Healthy: 16 children- The template operator
fetch(eZTemplateExecuteOperator::modify(),lib/eztemplate/classes/eztemplateexecuteoperator.php) callseZFunctionHandler::execute( 'content', 'node', array( 'node_id' => 89 ) ). eZFunctionHandler::moduleFunctionInfo( 'content' )(lib/ezutils/classes/ezfunctionhandler.php) creates aneZModuleFunctionInfofor the module once per request and keeps it in$GLOBALS['eZGlobalModuleFunctionList'].eZModuleFunctionInfo::loadDefinition()(lib/ezutils/classes/ezmodulefunctioninfo.php) looks for<repository>/content/function_definition.phpin each module repository and includes the first one it finds. That file sets$FunctionList.execute()looks upnodein$FunctionList, builds the argument list from the definition'sparametersin their order (your value, else thedefault, elsenull; a missing required parameter stops the call), and callscall_methodclass::methodon an instance of the class.- The method returns
array( 'result' => ... ); the template gets theresult.
function_definition.php
The kernel's kernel/content/function_definition.php, entry node, shows the format:
$FunctionList['node'] = array( 'name' => 'node',
'operation_types' => array( 'read' ),
'call_method' => array( 'class' => 'eZContentFunctionCollection',
'method' => 'fetchContentNode' ),
'parameter_type' => 'standard',
'parameters' => array( array( 'name' => 'node_id',
'type' => 'integer',
'required' => false,
'default' => false ),
array( 'name' => 'node_path',
'type' => 'string',
'required' => false,
'default' => false ),
// ...
) );| Key | Meaning |
|---|---|
name | the function name, as used in fetch() |
operation_types | read for every fetch function in the kernel; documentation |
call_method | class and method that do the work; the class is created with new and must be autoloadable |
parameter_type | standard |
parameters | in the order of the method's arguments: name, type, required, default |
The template passes parameters by name in a hash; the definition maps them to the method's arguments by
position. type is not checked; a parameter of type array matters to fetch aliases,
which split such constants at ;.
The function collection
By convention one class per module holds all its fetch functions, the "function collection"
(eZContentFunctionCollection in kernel/content/ezcontentfunctioncollection.php). Each method returns an array:
array( 'result' => $value )on success: the template gets$value;array( 'error' => array( 'error_type' => 'kernel', 'error_code' => eZError::KERNEL_NOT_FOUND ) )when the thing does not exist or may not be read: the template getsnull.
Check permissions in the method (canRead() on nodes and objects, or the user's policies): a template can call
any fetch function with any parameters, and the kernel's collections filter by the current user's access.
When something is wrong
Fetch never stops the page. Every problem is written to the debug output and the template gets null:
[fetch( 'nosuchmodule', 'list', hash() )]Output:
[](Missing function definition file for module: nosuchmodule and
Cannot execute function 'list' in module 'nosuchmodule', no valid data.)
def $result = fetch( 'content', 'nosuchfunction', hash() )
if is_null( $result )no result/ifOutput:
no result(No such function 'nosuchfunction' in module 'content'.)
def $result = fetch( 'content', 'node', hash() )
if is_null( $result )no resultelse$result.name|wash/ifOutput:
no resultcontent/node has no required parameter, so this call reaches eZContentFunctionCollection::fetchContentNode(),
which returns no node without a node id or path. For a function with a required parameter left out,
eZModuleFunctionInfo::executeClassMethod() does not call the method at all and the debug output says which
parameter was missing. Test fetch results with is_null() or is_set() before using them.
Where modules are found
The module repositories come from module.ini [ModuleSettings]: ModuleRepositories[] (the kernel's kernel
and kernel/private/modules) and, for each extension listed in ExtensionRepositories[] and active, its
modules folder. eZModule::activeModuleRepositories() (lib/ezutils/classes/ezmodule.php) merges the two lists
and reverses them, so extensions are searched before the kernel, and of the extensions the one listed last in
ExtensionRepositories[] first. On the site siteaccess the search order starts with
extension/sevenx_alpha_settings/modules, extension/expui/modules, extension/ezjscore/modules, ... and ends
with kernel/private/modules and kernel; 28 modules have fetch functions, 21 of them in the kernel. No extension
on a development installation has a function_definition.php for a kernel module.
Adding a fetch module in an extension
A module that only offers fetch functions needs no views: a folder with a function_definition.php is enough.
extension/myextension/modules/c11demo/function_definition.php
extension/myextension/classes/c11demofunctioncollection.php
extension/myextension/settings/module.ini.append.phpmodule.ini.append.php:
[ModuleSettings]
ExtensionRepositories[]=myextension
*/ ?>The definition (modules/c11demo/function_definition.php):
$FunctionList = array();
$FunctionList['children_by_class'] = array(
'name' => 'children_by_class',
'operation_types' => array( 'read' ),
'call_method' => array( 'class' => 'C11DemoFunctionCollection',
'method' => 'fetchChildrenByClass' ),
'parameter_type' => 'standard',
'parameters' => array( array( 'name' => 'parent_node_id',
'type' => 'integer',
'required' => true ),
array( 'name' => 'limit',
'type' => 'integer',
'required' => false,
'default' => 50 ) ) );The collection (classes/c11demofunctioncollection.php, then php bin/php/ezpgenerateautoloads.php -e):
class C11DemoFunctionCollection
{
/**
* The children of a node, counted per class identifier: array( identifier => count ), largest first.
*/
public function fetchChildrenByClass( $parentNodeID, $limit )
{
$parent = eZContentObjectTreeNode::fetch( (int)$parentNodeID );
if ( !$parent instanceof eZContentObjectTreeNode || !$parent->canRead() )
return array( 'error' => array( 'error_type' => 'kernel', 'error_code' => eZError::KERNEL_NOT_FOUND ) );
$counts = array();
foreach ( $parent->subTree( array( 'Depth' => 1, 'Limit' => (int)$limit ) ) as $child )
{
$identifier = $child->attribute( 'class_identifier' );
$counts[$identifier] = ( $counts[$identifier] ?? 0 ) + 1;
}
arsort( $counts );
return array( 'result' => $counts );
}
}Not run on a development installation as an installed extension: the script that rendered the examples in this and the next two
sections put a scratch module repository in front of the others in memory (eZModule::setGlobalPathList(), for its
own process) and loaded the classes from a scratch folder.
foreach fetch( 'c11demo', 'children_by_class', hash( 'parent_node_id', 89 ) ) as $identifier => $count
$identifier: $count
/foreachOutput:
ng_landing_page: 5
ng_topic: 4
ng_article: 2
ng_category: 1
ng_contact_form: 1
ng_container: 1
ng_site_info: 1
folder: 1Extending a kernel module's fetch functions
Because only the first function_definition.php found for a module name is read, an extension cannot add one
fetch function to content or section by shipping a file with just that function: it would hide all the
kernel's. Include the kernel's file and add to its list:
/**
* Extends the kernel's section fetch functions instead of replacing them: the first function_definition.php
* found for a module is the only one read, so this file includes the kernel's and adds to its list.
*/
include 'kernel/section/function_definition.php';
$FunctionList['c11_identifiers'] = array(
'name' => 'c11_identifiers',
'operation_types' => array( 'read' ),
'call_method' => array( 'class' => 'C11DemoSectionFunctionCollection',
'method' => 'fetchIdentifiers' ),
'parameter_type' => 'standard',
'parameters' => array() );(extension/myextension/modules/section/function_definition.php; C11DemoSectionFunctionCollection::fetchIdentifiers()
returns the sorted identifiers of eZSection::fetchList().) Both the kernel's section/list and the new function
work:
{def $sections = fetch( 'section', 'list' )
$identifiers = fetch( 'section', 'c11_identifiers' )}
section/list: $sections|count sections
section/c11_identifiers: $identifiers|implode( ', ' )Output:
section/list: 10 sections
section/c11_identifiers: , , design, help_internal, help_public, media, sample_collaboration, setup, standard, users(Two sections on a development installation have an empty identifier, hence the two leading commas.) The include path is relative to the installation root, which is the working directory of every Exponential request and script.
Even so, a function with your own module name (fetch( 'myext', ... )) is the better choice: it cannot collide
with a future kernel function of the same name, and it needs no copy of a kernel file's behavior.
Replacing them, and why not to
The same file without the include replaces the kernel's section fetch functions on every siteaccess where the
extension is active:
{def $sections = fetch( 'section', 'list' )
$identifiers = fetch( 'section', 'c11_identifiers' )}
section/list: $sections|count sections
section/c11_identifiers: $identifiers|implode( ', ' )Output:
section/list: 0 sections
section/c11_identifiers: , , design, help_internal, help_public, media, sample_collaboration, setup, standard, userssection/list now returns nothing; every template that uses it, in the admin too, silently breaks. If you must
change what a kernel fetch function returns, keep its name and parameters, include the kernel's file, and replace
only that entry's call_method with a subclass of the kernel's collection that calls parent:: for the cases you do
not change. Re-check it on every kernel upgrade.
Compiled templates
For fetch() calls whose module and function names are constants, the compiler (eZTemplateExecuteOperator::fetchTransform())
reads the function definition at compile time and writes a direct call of the collection method into the compiled
template, guarded by class_exists()/method_exists(). Two consequences:
- after changing a
function_definition.php(parameters, order,call_method), clear the template cache (php bin/php/ezcache.php --clear-tag=template --allow-root-user), or compiled templates keep calling the old method with the old argument order; - a definition that cannot be loaded at compile time (class missing) makes the compiler fall back to a run-time call.
All examples on this page gave the same result interpreted and compiled (into a scratch folder) on a development installation.
Related pages
- Fetch aliases
- Execute operators: the
fetchandfetch_aliasoperators - Fetch function reference
- Writing your own fetch function
- Fetch functions: reading data in templates
- 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.