Эта страница будет полезной для разработчиков, которым необходимо создать свои собственные теги или фильтры. Она позволит понять, каким образом WACT-компилирует шаблоны. Мы подробно разберем процесс компиляции шаблонов, укажем на основные классы, участвующие в компиляции, покажем, как генерится php-код компилированных шаблонов, как отрабатывают выражения для вывода данных и т.д.
Очень глубоких деталей реализации WACT-компилятора мы все же касаться не будем, но полученных знаний будет достаточно чтобы создать новый тег или фильтр.
Мы предполагаем, что Вам знаком материал раздела "Компиляция и выполнение шаблона. Активные компоненты шаблона. Дерево контейнеров данных".
Вы уже знаете, что WACT обрабатывает шаблоны в две стадии:
На этапе компиляции шаблон собирается целиком и переводится в php-скрипт с 2-мя функциями и зоной include-ов, подключающих классы, необходимые для нормальной отработки шаблона.
После создания дерева компиляции, компилятор проходит по этому дереву и просит каждый из компонентов фазы компиляции добавить что-либо в компилируемый шаблон. В итоге мы получаем php-скрипт с 2-мя функциями:.
Во время фазы исполнения (RunTime) запускается сначала инициализирующая функция шаблона и создается еще дерево активных компонентов фазы исполнения. Элементы этого дерева - это объекты класса WactRuntimeComponent (limb/wact/src/components/WactRuntimeComponent.class.php). К любому элементу этого дерева вы можете получить доступ через API шаблонной системы. Это позволяет контролировать певедение компонентов фазы исполнения.
Между деревом фазы компиляции и деревов активных компонентов фазы исполнения есть прямая связь, однако не все элементы дерева фазы компиляции имеют соответствующий активный компонент фазы исполнения.
При рендеринге шаблона происходит выполнение второй, выполняющей, функции откомпилированного шаблона.
Схематично схему работы шаблонизатора можно представить следующим образом:
Мы подробно опишем, как происходит компиляция на примере <list:list> и <list:item> тегов.
Рассмотрим простейший пример вывода на экран произвольного списка заголовков:
<html> <body> <h1>List example</h1> <list:list id="items"> <table border="0"> <list:item> <tr> <td>{$title}</td> </tr> </list:item> </table> </list:list> </body> </html>
Теперь мы подробно покажем как WACT откомпилирует данный шаблон.
Процесс компиляции состоит из нескольких этапов:
Весь процесс компиляции управляется из класса WactCompiler (limb/wact/src/compiler/WactCompiler.class.php).
Во время компиляции шаблон анализируется и создается так называемое дерево фазы компиляции.
Это дерево состоит из элементов - нодов. Базовый класс для всех нодов дерева компиляции - WactCompilerNode, limb/wact/src/compiler/compile_tree_node/WactCompilerNode.class.php).
В это дерево в качестве элементов попадают практически все элементы шаблона:
В нашем примере мы получим следующее дерево:
Root | |-WactTextNode | |-WactListListTag | | | |-WactTextNode | | | |-WactListItemTag | | | | | |-WactTextNode | | | | | |-WactOutputExpression | | | | | |-WactTextNode | | | |-WactTextNode | |-WactTextNode
Класс WactCompileTreeNode - это основной класс ноды дерева фазы компиляции.
Каждый объект класса WactCompileTreeNode содержит следующие атрибуты:
WactCompileTreeNode содержит необходимые методы для навигации по дереву и поиску необходимых элементов:
Класс WactCompileTreeNode также содержит набор методов, которые участвуют в процессе компиляции:
Параметр $code_writer содержит код откомпилированного шаблона.
Метод WactCompileTreeNode :: generateConstructor($code_writer) перекрывается в первый раз только в классе WactRuntimeComponentTag.
Еще стоит упомянуть следующие важные методы класса WactCompileTreeNode:
WactCompileTreeNode :: getComponentRefCode() перекрыт в классе WactRuntimeComponentTag и WactCompileTreeRootNode. Методы isDataSource() и getDataSource() перекрыты в WactRuntimeComponentDatasourceTag и в WactCompileTreeRootNode.
Параметр $code_writer методов генерации класса WactCompileTreeNode - это объект класса WactCodeWriter (limb/wact/src/compiler/WactCodeWriter.class.php).
Класс WactCodeWriter содержит следующие часто используемые методы:
Класс WactCodeWriter автоматически переключает контекст с html на php. Поэтому вы можете легко смешивать вызовы writeHtml() и writePhp().
Класс WactCompilerTag - это основной класс для тегов. Он наследуется от WactCompilerTreeNode, но добавляет функционал по работе с атрибутами тегов.
Вот список наиболее значительных методов для работы с атрибутами:
Кроме этого класс WactCompilerTag содержит набор методов, которые его наследники используют при генерации:
Вот так будет выглядеть код откомпилированного шаблона, который может быть получен после компиляции шаблона, использованного в примере (мы немного поправили его форматирование для улучшения читабельности) :
<?php require_once 'limb/wact/src/components/list/WactListComponent.class.php'; ?> <?php function tpl35fd34aefe6e79d5c94f7cd0e40644ef1($root, &$components) { $A = new WactListComponent('items'); $components['A'] = $A; $root->addChild($A); $B = new WactDatasourceRuntimeComponent('id0017'); $components['B'] = $B; $components['A']->addChild($B); } function tpl35fd34aefe6e79d5c94f7cd0e40644ef2($root, &$components) { $template = $root; ?> <html> <body> <h1 align="center">List example</h1> <?php $components['A']->rewind(); if ($components['A']->valid()) { ?> <table border="0"> <?php do { $components['B']->registerDataSource($components['A']->current()); ?> <tr> <td><?php echo htmlspecialchars($components['B']->get('title'), ENT_QUOTES); ?></td> </tr> <?php $components['A']->next(); } while ($components['A']->valid()); ?> </table> <?php } ?> </body> </html> <?php } $GLOBALS['TemplateRender'][$compiled_template_path] = 'tpl35fd34aefe6e79d5c94f7cd0e40644ef2'; $GLOBALS['TemplateConstruct'][$compiled_template_path] = 'tpl35fd34aefe6e79d5c94f7cd0e40644ef1'; ?>
Откомпилированный шаблон состоит из 4 частей:
Обратите внимание на инициализирующую функцию. Все активные компоненты, помимо регистрации в своих родительских элементах, также регистрируются в массиве $components, который также является параметром выполняющей функции. Сделано это из соображения эффективности обращения к активным компонентам внутри откомпилированным шаблонам. Код вида $components['A'] генерится в методе getComponentRefCode() класса WactRuntimeComponentTag.
Рассмотрим тег <list:list> и на его примере продемонстрируем, каким образом формируется компилированный шаблон.
Код класса WactListListTag можно найти в файле limb/wact/src/tags/list/list.tag.php:
<?php /** * The parent compile time component for lists * @tag list:list */ class WactListListTag extends WactRuntimeComponentTag { protected $runtimeIncludeFile = 'limb/wact/src/components/list/WactListComponent.class.php'; protected $runtimeComponentName = 'WactListComponent'; function generateTagContent($code_writer) { if ($this->hasAttribute('from')) { $code_writer->writePHP($this->getComponentRefCode() . '->registerDataset('); $this->attributeNodes['from']->generateExpression($code_writer); $code_writer->writePHP(');' . "\n"); } $code_writer->writePHP($this->getComponentRefCode() . '->rewind();' . "\n"); $code_writer->writePHP('if (' . $this->getComponentRefCode() . '->valid()) {' . "\n"); parent :: generateTagContent($code_writer); $code_writer->writePHP('}' . "\n"); $emptyChild = $this->findImmediateChildByClass('WactListDefaultTag'); if ($emptyChild) { $code_writer->writePHP(' else { ' . "\n"); $emptyChild->generateNow($code_writer); $code_writer->writePHP('}' . "\n"); } } } ?>
Разберем подробно код класса.
Итак:
class WactListListTag extends WactRuntimeComponentTag { protected $runtimeIncludeFile = 'limb/wact/src/components/list/WactListComponent.class.php'; protected $runtimeComponentName = 'WactListComponent'; [...] }
WactListListTag является потомком от WactRuntimeComponentTag, то есть тегом с активным компонентом фазы исполнения. Это приведек к генерации необходимого кода в инициализирующей функции. Защищенный атрибут класса $runtimeComponentName указывает как то, какого класса активный компонент следует создать. В итоге в откомпилированной шаблоне мы получили:
$A = new WactListComponent('items'); $components['A'] = $A; $root->addChild($A);
Защищенный атрибут класса $runtimeIncludeFile указывает компилятору шаблонов, какой файл следуют подключить. В откомпилированном шаблоне мы получили:
<?php require_once 'limb/wact/src/components/list/WactListComponent.class.php';?>
Генерация кода обычно производится в методе generateTagContent($code_writer) (об этом мы уже указывали в описании класса WactCompileTreeNode).
Рассмотрим следующие строки класса WactListListTag:
function generateTagContent($code_writer) { [...] $code_writer->writePHP($this->getComponentRefCode() . '->rewind();'); $code_writer->writePHP('if (' . $this->getComponentRefCode() . '->valid()) {'); parent :: generateTagContent($code_writer); $code_writer->writePHP('}' . "\n"); [...] }
Здесь в компилируемый шаблон по сути вставляется перемотка внутренного контейнера данных (итератора) на начало и условие на то, чтобы этот итератор содержал данные.
Обратите внимание на код $this→getComponentRefCode() - этот метод код, который позволяет обращаться к активному компоненту тега в откомпилированном шаблоне, для нашего примера он вернет что-то вида $components['A']
В результате в компилированном шаблоне мы получили следующий код:
<?php $components['A']->rewind(); if ($components['A']->valid()) { ?> [... откомпилированное содержимое внутри тега <list:list>] } <?php } ?>
В <list:list> теге динамические выражения используются для атрибута from, который позволяет указывать, откуда взять данные для списка. Побробнее об использовании from атрибута можно прочитать в разделе "Передача данных внутри шаблонов".
Код, который отвечает за атрибут from в классе WactListListTag:
function generateTagContent($code_writer) { if ($this->hasAttribute('from')) { $code_writer->writePHP($this->getComponentRefCode() . '->registerDataset('); $this->attributeNodes['from']->generateExpression($code_writer); $code_writer->writePHP(');' . "\n"); } [...] }
Поясним некоторые моменты:
Следующие три строки регистрируют нужные нам данные в активном компоненте <list:list> тега. Другие словами они заполняют контейнер данных:
$code_writer->writePHP($this->getComponentRefCode() . '->registerDataset('); $this->attributeNodes['from']->generateExpression($code_writer); $code_writer->writePHP(');'. "\n");
Так как from является динамическим, то для его генерации используется метод generateExpression($code_writer).
Допустим у нас есть шаблон:
<list:list id="news" from='{$#article.author.books}'> [...] </list:list>
В результате в откомпилированном шаблоне мы получим:
$A1 = WactTemplate :: makeObject($root, 'article'); $A2 = WactTemplate :: makeObject($A1, 'author'); $components['A']->registerDataset($A2->get('books'));
Теперь кратко покажем, как генерят компилированный шаблон остальные элементы дерева компиляции.
Все, что не является WACT-тегами, выраженями или php-кодом, WACT интерпретирует как обычный текст и выдает его в компилированный шаблон «как есть», то есть внутри класса WactTextNode есть такой код:
function generate($code_writer) { $code_writer->writeHTML($this->contents); parent :: generate($code_writer); } } ?>
где $this→contents - и есть сам текст.
Все, что заключено в рамки <?php ?> интерпретируется WACT-ом как php-элемент дерева компиляции и отдается в шаблон как есть (WactPhpNode):
function generate($code_writer) { $code_writer->writePHP($this->contents); } } ?>
По-умолчанию выражения (если они не являются атрибутами WACT-тегов) формируют в компилированном шаблоне код <?php echo …; ?>. Остальное зависит от фильтров и от сложности самого выражения. Например, выражение {$title} фактически означает применение фильтра html к данным (этот фильтр применяетя по-умолчанию), а title - это простейшее DBE. В результате из {$title} в откомпилированном шаблоне получим следующее:
<?php echo htmlspecialchars($components['B']->get('title'), ENT_QUOTES); ?>
Обсуждение