Содержание

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

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

Эта страница будет полезной для разработчиков, которым необходимо создать свои собственные теги или фильтры. Она позволит понять, каким образом 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 содержит необходимые методы для навигации по дереву и поиску необходимых элементов:

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

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

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

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

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

Класс WactCodeWriter

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

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

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

Класс WactCompilerTag

Класс 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.

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