This page is essential for developers who want to create their own {{macro}} tags or filters. After reading this page you will have a better understanding of the {{macro}} templates compilation process.
It is considered that you have already read the "{{macro}} compilation and rendering. How to run {{macro}} template." page.
{{macro}} processes templates in two stages:
At compile time {{macro}}:
The nodes of the compile time tree are objects of lmbMacroNode class or his descendants. While walking this tree, {{macro}} compiler calls generate(lmbMacroCodeWriter $code_writer) method for each node. This method accepts $code_writer argument which is an object of lmbMacroCodeWriter class. lmbMacroCodeWriter is responsible for accumulating all generated PHP code and in the end of the compilation process it simply writes the result into file. This file is a PHP version of the original template.
When you call lmbMacroTemplate :: render($var = array()) {{macro}} first searches for the compiled version of the template. If it exists and force compile setting is OFF then {{macro}} runs render() method of the generated PHP class described above without template recompilation. This speeds up things dramatically. _init() method is invoked before render() executes the rest of the generated code. Think of _init() method as of a kind of constructor for the compiled {{macro}} template. For example, _init() is used by form and {{pager}} tags.
Let's get into some dirty details of {{macro}} PHP code generation process using tag {{paginate}} as an example.
Here is a {{macro}} template:
<html> <body> <h1>List example</h1> {{paginate iterator='$#items' pager='my_pager'}} {{pager id="my_pager" items="5"}} {{pager:prev}}<a href="{$href}">Prev page</a>{{pager:prev}} {{pager:next}}<a href="{$href}">Next page</a>{{pager:next}} {{/pager}} {{list using="$#items"}} <table border="0"> {{list:item}} <tr> <td>{$item.title}</td> </tr> {{/list:item}} </table> {{/list}} </body> </html>
lmbMacroCompiler class is responsible for template compilation process(limb/macro/src/compiler/lmbMacroCompiler.class.php).
As we already mentioned above, compile time tree consists of nodes. The nodes are objects of lmbMacroNode class that can be found in limb/macro/src/compiler/lmbMacroNode.class.php.
The tree may contain the following types of nodes:
In the example below the following compile time tree will be built:
Root | |-lmbMacroTextNode | |-lmbMacroPaginateTag | |-lmbMacroPagerTag | | | |-lmbMacroPagerPrevTag | | | | | |-lmbMacroTextNode | | | |-lmbMacroPagerNextTag | | | |-lmbMacroTextNode | |-lmbMacroListTag | | | |-lmbMacroTextNode | | | |-lmbMacroListItemTag | | | | | |-lmbMacroTextNode | | | | | |-lmbMacroOutputExpressionNode | | | | | |-lmbMacroTextNode | | | |-lmbMacroTextNode | |-lmbMacroTextNode
lmbMacroNode - is a core class for all compile time tree nodes.
lmbMacroNode class has the following attributes:
lmbMacroNode has the following methods for tree browsing and searching:
lmbMacroNode also has a method executed in order to generate code of the compiled template:
lmbMacroCodeWriter class can be found in limb/macro/src/compiler/lmbMacroCodeWriter .class.php
lmbMacroCodeWriter has the following most frequently used methods:
lmbMacroCodeWriter takes care of proper switching of the write mode from HTML to PHP(and back) so you can call writeHTML() and writePHP() in any order.
lmbMacroTag class - is a base class for {{macro}} tags. lmbMacroTag is a descendant of lmbMacroNode and also contains functionality required to work with attributes.
Here is the list of method you will probably want to use the most:
lmbMacroTag also has a bunch of methods that child classes should override in order to generate compiled template code:
We strongly recommend not to override generate($code) method in descendants of lmbMacroTag since lmbMacroTag :: generate($code) contains essential functionality required for correct processing of complex attributes (call called attributes pre-generation).
Let's examine tag {{paginate}} class and see how {{macro}} tags generate PHP code.
Tag {{paginate}} is implemented as lmbMacroPaginateTag class that can be found in limb/macro/src/tags/pager/paginate.tag.php:
<?php /** * Applies pager to iterator (so called "pagination") * @tag paginate * @req_attributes iterator * @package macro * @version $Id$ */ class lmbMacroPaginateTag extends lmbMacroTag { protected function _generateContent($code) { $iterator = $this->get('iterator'); if($this->has('pager')) { if(!$pager_tag = $this->parent->findUpChild($this->get('pager'))) $this->raise('Can\'t find pager by "pager" attribute in {{paginate}} tag'); $pager = $pager_tag->getRuntimeVar(); if($this->has('limit')) $code->writePhp("{$pager}->setItemsPerPage({$this->get('limit')});\n"); $code->writePhp("{$pager}->setTotalItems({$iterator}->count());\n"); $code->writePhp("{$pager}->prepare();\n"); $offset = $code->generateVar(); $code->writePhp("{$offset} = {$pager}->getCurrentPageBeginItem();\n"); $code->writePhp("if({$offset} > 0) {$offset} = {$offset} - 1;\n"); $code->writePhp("{$iterator}->paginate({$offset}, {$pager}->getItemsPerPage());\n"); return; } elseif($this->has('offset')) { if(!$this->has('limit')) $this->raise('"limit" attribute for {{paginate}} is required if "offset" is given'); $code->writePhp("{$iterator}->paginate({$this->get('offset')},{$this->get('limit')});\n"); return; } elseif($this->has('limit')) { $code->writePhp("{$iterator}->paginate(0,{$this->get('limit')});\n"); return; } } }
lmbMacroPaginateTag is a descendant of lmbMacroTag, so {{paginate}} is just a regular tag (unlike, for example, {{form}} or {{pager}} that create runtime helpers).
Code generation happens in _generateContent($code_writer) method which was mentioned above.
Take a look at these lines:
if($this->has('pager')) { if(!$pager_tag = $this->parent->findUpChild($this->get('pager'))) $this->raise('Can\'t find pager by "pager" attribute in {{paginate}} tag'); $pager = $pager_tag->getRuntimeVar(); [..]
If tag has pager attribute then it tries to find an appropriate pager node by calling findUpChild() for his parent. lmbMacroTag :: raise($message) used to throw exceptions of lmbMacroException class with meta-info from $location ($location is object of lmbMacroTemplateLocation, see lmbMacroNode description above).
Let's go further:
$pager = $pager_tag->getRuntimeVar(); if($this->has('limit')) $code->writePhp("{$pager}->setItemsPerPage({$this->get('limit')});\n");
The first line returns a name of the variable that will point at pager helper object at runtime.
The second line writes a line of PHP code that will set pager items limit for one page at runtime. Since limit attribute value will be a number or a variable in most cases, we don't need to use getEscaped($attr_name) for it.
$code->writePhp("{$pager}->setTotalItems({$iterator}->count());\n"); $code->writePhp("{$pager}->prepare();\n"); $offset = $code->generateVar(); $code->writePhp("{$offset} = {$pager}->getCurrentPageBeginItem();\n"); $code->writePhp("if({$offset} > 0) {$offset} = {$offset} - 1;\n"); $code->writePhp("{$iterator}->paginate({$offset}, {$pager}->getItemsPerPage());\n"); return;
An interesting moment here is the usage of lmbMacroCodeWriter :: generateVar() which generates a unique temporary variable for $offset.
The compiled code should look something like this:
<?php $this->pager_pager->setTotalItems($this->photos->count()); $this->pager_pager->prepare(); $EX = $this->pager_pager->getCurrentPageBeginItem(); if($EX > 0) $EX = $EX - 1; $this->photos->paginate($EX, $this->pager_pager->getItemsPerPage()); ?>
Any element of {{macro}} template that is not a tag and not an output expression is considered to be a node of lmbMacroTextNode class. lmbMacroTextNode generates contents «as is». Basically, lmbMacroTextNode does the following:
function generate($code_writer) { $code_writer->writeHTML($this->contents); parent :: generate($code_writer); } } ?>
Output expressions generate a PHP block with echo operation: <?php echo …; ?>. The rest depends on filters usage and on the complexity of the expression. For example, the expression {$title|html} will generate the following code:
<?php echo htmlspecialchars($title, ENT_QUOTES); ?>
Обсуждение