Инструменты пользователя

Инструменты сайта


limb3:ru:packages:wact:compiler

Как работает компилятор WACT-шаблонов

Цель этой страницы

Эта страница будет полезной для разработчиков, которым необходимо создать свои собственные теги или фильтры. Она позволит понять, каким образом WACT-компилирует шаблоны. Мы подробно разберем процесс компиляции шаблонов, укажем на основные классы, участвующие в компиляции, покажем, как генерится php-код компилированных шаблонов, как отрабатывают выражения для вывода данных и т.д.

Очень глубоких деталей реализации WACT-компилятора мы все же касаться не будем, но полученных знаний будет достаточно чтобы создать новый тег или фильтр.

Мы предполагаем, что Вам знаком материал раздела "Компиляция и выполнение шаблона. Активные компоненты шаблона. Дерево контейнеров данных".

Фаза компиляции и фаза исполнения

Вы уже знаете, что WACT обрабатывает шаблоны в две стадии:

  • фаза компиляции (CompileTime) и
  • фаза исполнения (RunTime).

На этапе компиляции шаблон собирается целиком и переводится в 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 откомпилирует данный шаблон.

Этапы компиляции шаблона

Процесс компиляции состоит из нескольких этапов:

  • Анализ шаблона и составление дерева компиляции
  • Создание инициализирующей функции
  • Проход по дереву и генерация php-кода в инициализирующей функции. Этот код создает иерархию активных компонентов фазы выполнения. Каждый элемент дерева, которому требуется активный компонент, добавляет в эту функцию свой код.
  • Во время первого прохода создается список файлов, которые необходимо подключить до выполнения шаблона.
  • Создание выполняющей функции.
  • Второй проход по дереву и генерация выполняющего php-кода. Каждый элемент дерева имеет возможность дописать необходимый php-код или просто текст в эту выполняющую функцию.
  • Запись в файл компилирующего шаблона списка необходимых файлов.
  • Запись в файл компилирующего фаблона инициализующей и выполняющей функции.
  • Запись в файл компилирующего фаблона имен инициализирующей и выполняющей функции.

Весь процесс компиляции управляется из класса WactCompiler (limb/wact/src/compiler/WactCompiler.class.php).

Формирование дерева этапа компиляции

Во время компиляции шаблон анализируется и создается так называемое дерево фазы компиляции.

Это дерево состоит из элементов - нодов. Базовый класс для всех нодов дерева компиляции - WactCompilerNode (limb/wact/src/compiler/compile_tree_node/WactCompilerNode.class.php).

В это дерево в качестве элементов попадают практически все элементы шаблона:

  • Обычный текст и HTML-код в виде обычных WactTextNode (limb/wact/src/compiler/compile_tree_node/WactTextNode.class.php)
  • PHP-код в виде WactPHPNode (limb/wact/src/compiler/compile_tree_node/WactPhpNode.class.php)
  • WACT-теги различных типов. Базовый класс для тега - WactCompilerTag (limb/wact/src/compiler/tag_node/WactCompilerTag.class.php)
  • Выражения вида {$title} вместе с фильтрами в виде объектов класса WactOutputExpression (limb/wact/src/compiler/compile_tree_node/WactOutputExpression.class.php)

В нашем примере мы получим следующее дерево:

  Root
   |
   |-WactTextNode
   |
   |-WactListListTag
   |     |
   |     |-WactTextNode
   |     |
   |     |-WactListItemTag
   |     |   |
   |     |   |-WactTextNode
   |     |   |
   |     |   |-WactOutputExpression
   |     |   |
   |     |   |-WactTextNode
   |     |
   |     |-WactTextNode
   |
   |-WactTextNode

Класс WactCompileTreeNode

Класс WactCompileTreeNode - это основной класс ноды дерева фазы компиляции.

Каждый объект класса WactCompileTreeNode содержит следующие атрибуты:

  • children - массив дочерних элементов
  • parent - ссылку на родительский элемент.
  • ServerId - идентификатор элемента (генерится автоматически, если это необходимо).
  • location_in_template - объект класса WactSourceLocation, указывающий точное расположение данного элемента в начальных шаблонах.

WactCompileTreeNode содержит необходимые методы для навигации по дереву и поиску необходимых элементов:

  • findChild($ServerId) - найти дочерний элемент по идентификатору. Поиск производится вниз по иерархии по всему дереву.
  • findUpChild($ServerId) - найти элемент вверх по иерархии. Область поиска начинается с дочерних элементов текущего элемента и расширяется по мере продвижения вверх по иерархии, так как вызывает метод findChild().
  • findChildByClass($class) - аналогично findChild(), но ищет элементы по классу.
  • findChildrenByClass($class) - аналогично findChildByClass, но возвращает массив элементов указанного класса.
  • findImmediateChildByClass($class) - найти дочерний элемент, принадлежащий именно данному элементу, по названию класса.
  • getChildren() - возвращает массив дочерних элементов.
  • getParent() - возвращает ссылку на родительский элемент.

Класс WactCompileTreeNode также содержит набор методов, которые участвуют в процессе компиляции:

  • generateConstructor($code_writer) - должен генерить php-код, создающий активный компонент фазы компиляции. По-умолчанию метод реализован так, чтобы делегировать своим дочерним элементам.
  • generate($code_writer) - самый общий метод, в котором элементы дерева компиляции выполняют генерацию php-кода откомпиленного шаблона. Обычно дочерние классы редко перекрывают этот метод.
  • generateContent($code_writer) - метод, при помощи которого элементы дерева генерят свой код. Обычно перекрывается в дочерних классах.

Параметр $code_writer содержит код откомпилированного шаблона.

Метод WactCompileTreeNode :: generateConstructor($code_writer) перекрывается в первый раз только в классе WactRuntimeComponentTag.

Еще стоит упомянуть следующие важные методы класса WactCompileTreeNode:

  • getComponentRefCode() - возвращает php-код, необходимый для доступа к активному компоненту фазы исполнения внутри откомпилированного шаблона.
  • isDataSource() - возвращает true, если элемент дерева должен создавать активный компонент, являющийся контейнером данных. По-умолчанию, возвращет false.
  • getDataSource() - возращает ссылку на ближайший элемент, который создает активный компонент и является контейнером данных.

WactCompileTreeNode :: getComponentRefCode() перекрыт в классе WactRuntimeComponentTag и WactCompileTreeRootNode. Методы isDataSource() и getDataSource() перекрыты в WactRuntimeComponentDatasourceTag и в WactCompileTreeRootNode.

Класс WactCodeWriter

Параметр $code_writer методов генерации класса WactCompileTreeNode - это объект класса WactCodeWriter (limb/wact/src/compiler/WactCodeWriter.class.php).

Класс WactCodeWriter содержит следующие часто используемые методы:

  • writePhp($php) - добавить php-код
  • writeHtml($text) - добавить html-код или простой текст
  • registerInclude($include_file) - добавить путь до файла, который необходимо будет подключить до инициализации откомпилированного шаблона.
  • getTempVariable() - генерит уникальное имя переменной, которую можно использовать в откомпилированном шаблоне. Получается что-то типа А0001 (или короче)
  • getTempVarRef() - генерит уникальное имя переменной с символом $ впереди. Получается что-то типа $A0001 (или короче).

Класс WactCodeWriter автоматически переключает контекст с html на php. Поэтому вы можете легко смешивать вызовы writeHtml() и writePhp().

Класс WactCompilerTag

Класс WactCompilerTag - это основной класс для тегов. Он наследуется от WactCompilerTreeNode, но добавляет функционал по работе с атрибутами тегов.

Вот список наиболее значительных методов для работы с атрибутами:

  • getAttribute($name) - возвращает значение атрибута. Это значение должно быть константным, иначе произойдет ошибка. см. ниже пример для атрибута from тега <list:list>, что необходимо делать, сли значение атрибута не может быть определено на этапе компиляции.
  • getAttributeNode($attrib) - возвращет объект атрибута.
  • hasAttribute($attrib) - возвращает true, если тег имеет соответствующий атрибут.
  • getBoolAttribute($attrib, $default = FALSE) - возвращает boolean-значение атрибута. Возвращает false, если атрибут имеет значение FALSE, F, N, No, NONE, 0.
  • removeAttribute($attrib) - удаляет атрибут.

Кроме этого класс WactCompilerTag содержит набор методов, которые его наследники используют при генерации:

  • generateTagContent($code_writer) - используется для генерации содержимого тега. По-умолчанию реализован таким образом, что передает управление на генерацию контента дочерних элементов. Поэтому этот метод перекрывается в дочерних классах в том случае, если они не предполагают наличия других WACT-тегов или других элементов внутри.
  • generateBeforeContent($code_writer) - используется для генерации кода до содержимого тега.
  • generateAfterContent($code_writer) - используется для генерации кода после содержимого тега.

Содержимое откомпилированного шаблона

Вот так будет выглядеть код откомпилированного шаблона, который может быть получен после компиляции шаблона, использованного в примере (мы немного поправили его форматирование для улучшения читабельности) :

<?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 частей:

  • Включение нужных php-файлов. В нашем случае подключается только файл класса активного компонента <list:list> тега.
  • Инициализирующая функция, которая получает корневой активный компонент в качестве параметра. Это как бы конструктор откомпилированного шаблона. Обратите внимание на иерархию активных компонентов. Сначала в корень добавляется активный компонент тега <list:list> с идентификатором news. Затем в него добавляется активный компонент тега <list:item> с генерированным идентификатором.
  • Выполняющая функция, которая содержит код вывода обычного html-контента, а также тот код, который был сгенереген тегами по время компиляции. Чуть ниже мы покажем, как теги пишут этот код.
  • Регистрация названий функций шаблона в глобальной массиве для инициализации и выполнения шаблона.

Обратите внимание на инициализирующую функцию. Все активные компоненты, помимо регистрации в своих родительских элементах, также регистрируются в массиве $components, который также является параметром выполняющей функции. Сделано это из соображения эффективности обращения к активным компонентам внутри откомпилированным шаблонам. Код вида $components['A'] генерится в методе getComponentRefCode() класса WactRuntimeComponentTag.

Генерация php-кода выполняющей функции WACT-тегами

Рассмотрим тег <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 } ?>

Генерация 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'));

Генерация php-кода другими элементами шаблона

Теперь кратко покажем, как генерят компилированный шаблон остальные элементы дерева компиляции.

Обычный текст

Все, что не является WACT-тегами, выраженями или php-кодом, WACT интерпретирует как обычный текст и выдает его в компилированный шаблон «как есть», то есть внутри класса WactTextNode есть такой код:

  function generate($code_writer)
  {
    $code_writer->writeHTML($this->contents);
 
    parent :: generate($code_writer);
  }
}
?>

где $this→contents - и есть сам текст.

php-код

Все, что заключено в рамки <?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); ?>

Обсуждение

Ваш комментарий. Вики-синтаксис разрешён:
   ____   ___   __  __   ____     __
  / __/  / _ \ / / / /  / __/ __ / /
 / _/   / // // /_/ /  / _/  / // / 
/_/    /____/ \____/  /_/    \___/
 
limb3/ru/packages/wact/compiler.txt · Последние изменения: 2010/11/10 10:02 (внешнее изменение)