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

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


limb3_2007_2:ru:usage: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-код для выполняющей функции. Реализация в WactCompileTreeNode из 3-х вызовов методов:
    • generateContents($code_writer) - код, который должен выполниться до основной генерации. Обычно сюда помещают генерацию кода, который подготавливает какое-либо состояние.
    • preGenerate($code_writer) - непосредственная генерация кода элемента шаблона. Здесь производится вызов метода generate($code_writer) всех дочерних элементов, которые относятся к текущему элементу дерева.
    • postGenerate($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) - удаляет атрибут.

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

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

<?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 preGenerate($code_writer)
  {
    parent::preGenerate($code_writer);
 
    if ($this->hasAttribute('from'))
    {
      $this->generateDereference($code_writer, $this->getAttribute('from'));
    }
 
    $code_writer->writePHP($this->getComponentRefCode() . '->rewind();' . "\n");
    $code_writer->writePHP('if (' . $this->getComponentRefCode() . '->valid()) {' . "\n");
  }
 
  function generateDereference($code_writer, $from)
  {
    $from_dbe = new WactDataBindingExpression($from, $this);
    $from_dbe->generatePreStatement($code_writer);
 
    $code_writer->writePHP($this->getComponentRefCode() . '->registerDataset(');
 
    $from_dbe->generateExpression($code_writer);
 
    $code_writer->writePHP(');' . "\n");
 
    $from_dbe->generatePostStatement($code_writer);
  }
 
  function postGenerate($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");
    }
 
    parent::postGenerate($code_writer);
  }
}
?>

Разберем подробно код класса.

Итак:

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';?>

Генерация кода тегами производится в методе generate($code_writer) (об этом мы уже указывали в описании класса WactCompileTreeNode).

Расширение именно методов preGenerate и postGenerate в классе тега <list:list> в принципе не слишком обосновано и скорее всего имеет исторические корни. Тег <list:item> имеет другую реализацию - в нем все реализовано в методе generateContents.

Рассмотрим следующие строки класса WactListListTag:

  function preGenerate($code_writer)
  {
    [...]
    $code_writer->writePHP($this->getComponentRefCode() . '->rewind();');
    $code_writer->writePHP('if (' . $this->getComponentRefCode() . '->valid()) {');
  }
  [...]
  function postGenerate($code_writer)
  {
    $code_writer->writePHP('}');
    [...]
  }

Здесь в компилируемый шаблон по сути вставляется перемотка внутренного контейнера данных (итератора) на начало и условие на то, чтобы этот итератор содержал данные.

Обратите внимание на код $this→getComponentRefCode() - этот метод код, который позволяет обращаться к активному компоненту тега в откомпилированном шаблоне, для нашего примера он вернет что-то вида $components['A']

В результате в компилированном шаблоне мы получили следующий код:

 <?php $components['A']->rewind();
  if ($components['A']->valid()) { ?>
  [... откомпилированное содержимое внутри тега <list:list>]
   }
 <?php } ?>

Генерация php-кода для аттрибутов тегов, которые используют DBE

Data Binding Expressions (DBE) - это название механизма, который позволяет WACT-шаблонам указывать, какую именно переменную из из какого контейнера данных необходимо использовать.

DBE автоматически формируются в выражениях для вывода переменных. Если вы желаете использовать DBE для значений атрибутов тегов, тогда необходимо эти значения обворачивать в DBE самостоятельно.

В <list:list> теге DBE используются для атрибута from, который позволяет указывать, откуда взять данные для списка. Побробнее об использовании from атрибута можно прочитать в разделе "Передача данных внутри шаблонов".

Код, который отвечает за атрибут from в классе WactListListTag:

  function preGenerate($code_writer)
  {
    [...]
    if ($this->hasAttribute('from'))
    {
      $this->generateDereference($code_writer, $this->getAttribute('from'));
    }
    [...]
  }
 
  function generateDereference($code_writer, $from)
  {
    $from_dbe = new WactDataBindingExpression($from, $this);
    $from_dbe->generatePreStatement($code_writer);
 
    $code_writer->writePHP($this->getComponentRefCode() . '->registerDataset(');
 
    $from_dbe->generateExpression($code_writer);
 
    $code_writer->writePHP(');' . "\n");
 
    $from_dbe->generatePostStatement($code_writer);
  }

Поясним некоторые моменты:

$from_dbe = new WactDataBindingExpression($from, $this);

Создается объект DBE класса WactDataBindingExpression. В конструктор DBE передается выражение DBE и ссылка на контекст, начиная с которого нужно искать контейнер с данными (по сути это ссылка на текущий контейнер с данными).

$from_dbe->generatePreStatement($code_writer);

Эта строка создает в компилированном шаблоне код, который получает необходимый контейнер с данными, учитывая все модификаторы и путь до переменной. Поясним на примере. Допустим у нас есть шаблон:

<list:list id="news" from='#article.author.books'>
  [...]
</list:list>

То есть мы желаем получить стачала переменную article из корневого контейнера данных. Затем из article - переменную atuhroк и наконец - список $books, который и будет массивом данных для тега <list:list>. Так вот generatePreStatement() отвечает на все, что идет до .books. В результате в откомпилированном шаблоне мы получим:

$A1 = WactTemplate :: makeObject($root, 'article');
$A2 = WactTemplate :: makeObject($A1, 'author');

Следующие три строки регистрируют нужные нам данные в активном компоненте <list:list> тега. Другие словами они заполняют контейнер данных:

  $code_writer->writePHP($this->getComponentRefCode() . '->registerDataset(');
 
  $from_dbe->generateExpression($code_writer);
 
  $code_writer->writePHP(');');

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

$components['A']->registerDataset($A2->get('books'));

Ну и наконец строка:

$from_dbe->generatePostStatement($code_writer);

Вызов этого метода необходим для генерации какого завершающего кода.

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

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

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

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

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

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

php-код

Все, что заключено в рамки <?php ?> интерпретируется WACT-ом как php-элемент дерева компиляции и отдается в шаблон как есть (WactPhpNode):

  function generateContents($code_writer)
  {
    $code_writer->writePhp($this->contents);
 
    parent :: generateContents($code_writer);
  }
}
?>

Выражения и фильтры

По умолчанию выражения формируют в компилированном шаблоне только код <?php echo …; ?>. Все остальное зависит от фильтров. Например, выражение {$title} фактически означает применение фильтра html к данным (этот фильтр применяетя по-умолчанию), а title - это простейшее DBE. В результате из {$title} в откомпилированном шаблоне получим следующее:

<?php echo htmlspecialchars($components['B']->get('title'), ENT_QUOTES); ?>

Обсуждение

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