Шаблонизатор VMX::Template — различия между версиями

Материал из YourcmcWiki
Перейти к: навигация, поиск
м (Циклы)
 
(не показано 106 промежуточных версий этого же участника)
Строка 1: Строка 1:
Данный модуль представляет собой новую версию VMX::Template, построенную на некоторых новых идеях, ликвидировавшую безобразие и legacy-код, накопленный в [[Шаблонизатор VMX::Template/Старая версия|старой версии]], однако сохранившую высокую производительность и простоту.
+
'''VMX::Template''' — простой и высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии.
  
Есть [{{SVN|vitaphoto/branch/php/template.php}} PHP-версия] и [{{SVN|vitaphoto/branch/solstice/lib-sway/VMX/Template.pm}} Perl-версия] шаблонизатора. Реализация, естественно, несколько отличается по причине различий языков — например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь [http://xcache.lighttpd.net/ XCache] или [http://eaccelerator.net/ eAccelerator], ибо там сохранить coderef между запросами, по-видимому, невозможно.
+
* Лицензия: GNU GPL версии 3 или новее
 +
* Полный набор исходников здесь: http://yourcmc.ru/git/vitalif/VMXTemplate
 +
** PHP-версия (PHP >= 5.4): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.php template.php] и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.parser.php template.parser.php].
 +
** Новая Perl-версия: [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMXTemplate.pm VMXTemplate.pm] и все его [http://yourcmc.ru/git/tree/VMXTemplate.git/master/VMXTemplate подмодули VMXTemplate/*.pm].
 +
** Старая Perl-версия: [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMX%2FTemplate.pm VMX/Template.pm] и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMX%2FCommon.pm VMX/Common.pm].
 +
** Исходный код грамматики PHP-версии (LALR(1) [http://yourcmc.ru/git/vitalif/lime LIME]): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.lime template.lime].
 +
** Исходный код грамматики Perl-версии (LALR(1) {{CPAN|Parse::Yapp}}): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.yp template.yp] и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.skel.pm template.skel.pm].
 +
** Исходный код голой грамматики (LALR(1) yacc): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.y template.y].
 +
** В Git-репозитории можно найти полную историю разработки, начиная с самых старых phpbb-подобный версий.
 +
* Простые настройки для подсветки синтаксиса шаблонов в [http://www.midnight-commander.org/ Midnight Commander]'а: [{{SVN|vitalif/trunk/scripts/tpl.syntax|markup}} tpl.syntax]. Чтобы подсветка нормально выглядела, к tpl.syntax в начало надо дописать html.syntax из стандартного комплекта поставки mc.
  
Развивается то одна, то другая, в зависимости от проекта, над которым я работаю в моменте.
+
== Что это за шаблонизатор? ==
  
Также есть простенький (и кривоватенький) файл настроек синтаксиса шаблонов для [http://www.midnight-commander.org/ Midnight Commander]'а: [{{SVN|vitalif/trunk/scripts/tpl.syntax|markup}} tpl.syntax].
+
'''VMX::Template''' изначально реализован по мотивам примитивного шаблонизатора, взятого из кода форума phpBB 2 ([[/Старая версия|историческое описание старой версии]]), и с тех пор (с 2006 года) переписанного 4 раза:
 +
# На регулярные выражения →
 +
# На поиск подстроки →
 +
# На метод рекурсивного спуска →
 +
# И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME
  
Работаю над переводчиком с {{CPAN|Template::Toolkit}} на VMX::Template. (ибо TT — задрал, скотина!)
+
Есть PHP и Perl версии шаблонизатора, основная версия шаблонизатора — в данный момент PHP. Есть некоторые различия реализации — например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь [http://xcache.lighttpd.net/ XCache] или [http://eaccelerator.net/ eAccelerator], ибо там сохранить coderef между запросами невозможно.
  
<span style="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается».</span> Это к тому, что идея вообще-то схожая, но реализация гораздо проще и быстрее.
+
<span style="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается».</span>
  
== Идеи ==
+
Это к тому, что и идея, и синтаксис шаблонов вообще-то схожи, но сама реализация гораздо проще и быстрее.
  
Уйти от assign_vars(), assign_block_vars(). Передавать, как и в обычных движках, просто хеш с данными $vars. Как, например, в {{CPAN|Template::Toolkit}}. При этом сохранить данные методы для совместимости.
+
== Использование ==
  
Почистить синтаксис: ликвидировать «преобразования», «вложенный путь по переменной» (->key->index->key->и т. п.), специальный синтаксис для окончания SET, неочевидное обращение к счётчику block.#, tr_assign_* и т. п.
+
=== PHP ===
  
Переписать с нуля компилятор.
+
Требуется PHP версии не ниже 5.4.
  
Добавить в употребление функции, но только самые необходимые.
+
<code-php>
 
+
Добавить обработку ошибок и диагностические сообщения.
+
 
+
== Использование (PHP) ==
+
 
+
<source lang="php">
+
 
require_once 'template.php';
 
require_once 'template.php';
  
# Конструктор
+
# Конструктор (значения опций - по умолчанию)
$template = new Template(array(
+
$template = new VMXTemplate(array(
 
     'root'          => '.',        # директория с шаблонами
 
     'root'          => '.',        # директория с шаблонами
 
     'cache_dir'    => './cache',  # директория для кэширования компилированного кода шаблонов
 
     'cache_dir'    => './cache',  # директория для кэширования компилированного кода шаблонов
     'print_error'  => true,       # если true, ошибки компиляции выводятся на STDOUT
+
     'print_error'  => false,     # если true, ошибки компиляции выводятся на STDOUT
 
     'raise_error'  => false,      # если true, при ошибке компиляции вызывается die()
 
     'raise_error'  => false,      # если true, при ошибке компиляции вызывается die()
 +
    'log_error'    => false,      # если true, ошибки компиляции логгируются через error_log()
 
     'reload'        => true,      # если false, шаблоны будут считываться с диска только 1 раз, и вызовов stat() происходить не будет
 
     'reload'        => true,      # если false, шаблоны будут считываться с диска только 1 раз, и вызовов stat() происходить не будет
     # 'use_utf8'      => undef,   # (нужно только в Perl) шаблоны в UTF-8 и с флагом UTF-8 = On
+
     'use_utf8'      => true,       # если true, использовать кодировку UTF-8 для строковых операций
 
     'begin_code'    => '<!--',    # маркер начала директивы кода
 
     'begin_code'    => '<!--',    # маркер начала директивы кода
 
     'end_code'      => '-->',      # маркер конца директивы кода
 
     'end_code'      => '-->',      # маркер конца директивы кода
 
     'eat_code_line' => true,      # (похоже на TT CHOMP) съедать "лишний" перевод строки, если в строке только директива?
 
     'eat_code_line' => true,      # (похоже на TT CHOMP) съедать "лишний" перевод строки, если в строке только директива?
     'begin_subst'  => '{',        # маркер начала подстановки выражения
+
     'begin_subst'  => '{',        # маркер начала подстановки выражения (либо {, либо false)
     'end_subst'    => '}',        # маркер конца подстановки выражения
+
     'end_subst'    => '}',        # маркер конца подстановки выражения (либо }, либо false)
 
     'compiletime_functions' =>    # дополнительные компилируемые функции
 
     'compiletime_functions' =>    # дополнительные компилируемые функции
         array('func' => callback), # массив вида имя функции (в шаблонах) => callback,
+
         array('func' => callback), # хеш вида имя функции (в шаблонах) => callback($template, array $args),
 
                                   # которому передаются скомпилированные выражения всех аргументов
 
                                   # которому передаются скомпилированные выражения всех аргументов
     # немного legacy, устаревшее:
+
     'strip_space'  => true,      # встроенный фильтр: срезание пробелов из начал и концов строк
     'wrapper'      => NULL,      # если равно чему-то, что можно вызвать, через это будет
+
     'filters'      =>             # фильтры - выполняются над выводом всех шаблонов
                                  # пропущен вывод всех шаблонов ("глобальный фильтр")
+
        array(callback1, ...),
     'strict_end'   => false,      # требовать <!-- END имя_блока --> после <!-- BEGIN имя_блока -->
+
     'auto_escape'   => false,      # функция авто-экранирования, например "s" (для HTML-безопасного режима)
 
));
 
));
  
Строка 57: Строка 65:
 
   
 
   
 
# Выполнение шаблона и получение результата:
 
# Выполнение шаблона и получение результата:
# (возможно с передачей целого хеша данных)
+
# (возможно с передачей данных в шаблон)
$page = $template->parse('имя_файла' [, array("ключ" => "значение", ...)]);
+
$page = $template->parse('имя_файла.tpl' [, array("ключ" => "значение", ...)]);
  
# Аналогично выполнение именованного блока из файла:
+
# Выполнение именованного блока из файла:
$page = $template->parse('имя_файла', 'имя_блока' [, array("ключ" => "значение", ...)]);
+
$page = $template->exec_from('имя_файла.tpl', 'имя_блока' [, array("ключ" => "значение", ...)]);
 
   
 
   
# Аналогично выполнение кода из строки:
+
# Выполнение кода из строки:
$page = $template->parse(NULL, 'код' [, 'имя_блока'] [, array("ключ" => "значение", ...)]);
+
$page = $template->parse_inline('код' [, array("ключ" => "значение", ...)]);
 +
 
 +
# Выполнение именованного блока из кода (не рекомендуется, но возможно):
 +
$page = $template->exec_from_inline('код', 'имя_блока' [, array("ключ" => "значение", ...)]);
 
   
 
   
 
# Очистка сохранённых данных для генерации ещё одной страницы:
 
# Очистка сохранённых данных для генерации ещё одной страницы:
 
$template->clear;
 
$template->clear;
</source>
+
</code-php>
  
== Реализация ==
+
=== Perl ===
  
Маркеры начала и конца кода <tt><nowiki><!-- --></nowiki></tt> и подстановки <tt><nowiki>{ }</nowiki></tt> могут быть заменены любыми другими. Если, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>. Маркеры подстановки можно вообще убрать, ибо подстановка тоже является кодом.
+
Perl версия обновлена и теперь в точности соответствует PHP-версии.
  
Путь к переменной теперь может включать в себя числа. Это будут обращения к элементам массивов, в то время как всё остальное — обращения к элементам хешей.
+
<source lang="perl">
 +
use VMXTemplate;
  
=== Циклы ===
+
# Конструктор
 +
$template = new VMXTemplate(
 +
    'root'          => '.',        # директория с шаблонами
 +
    'cache_dir'    => undef,      # директория для кэширования компилированного кода шаблонов
 +
    'reload'        => 2,          # если 0, то шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
 +
                                  # если >0, то шаблоны будут перечитываться с диска не чаще чем раз в reload секунд
 +
    'print_error'  => 1,          # если TRUE, ошибки компиляции попадают в вывод шаблона
 +
    'log_error'    => 1,          # если TRUE, ошибки компиляции печатаются на STDERR
 +
    'raise_error'  => 0,          # если TRUE, при ошибке компиляции вызывается die()
 +
    'use_utf8'      => undef,      # если TRUE, использовать "use utf8" на шаблонах
 +
    'begin_code'    => '<!--',    # маркер начала директивы кода
 +
    'end_code'      => '-->',      # маркер конца директивы кода
 +
    'begin_subst'  => '{',        # маркер начала подстановки выражения
 +
    'end_subst'    => '}',        # маркер конца подстановки выражения
 +
    'eat_code_line' => 1,          # (похоже на TT CHOMP) если TRUE, съедать "лишний" перевод строки, если в строке только директива кода (begin_code..end_code)
 +
    'no_code_subst' => 1,          # если TRUE, выполнять директивы кода (begin_code..end_code), но игнорировать их результат
 +
    'compiletime_functions' =>    # дополнительные компилируемые функции
 +
        { 'func' => sub {} },      # хеш вида имя функции (в шаблонах) => coderef,
 +
                                  # которому передаются скомпилированные выражения всех аргументов и первым - парсер (объект VMXTemplate::Parser)
 +
    'filters' => [ sub {}, .. ],  # фильтры для запуска на выводе каждого внешнего шаблона (фильтр - функция, модифицирующая $_[0])
 +
    'strip_space'  => 0,          # если TRUE, удалять пробелы и табы из начала и конца всех строк вывода
 +
    'auto_escape'  => '',        # функция авто-экранирования, например "s" (для HTML-безопасного режима)
 +
);
  
Вне блока {block} будет иметь значение ARRAY(0x…), то есть массив всех итераций блока block, а {block.0} будет иметь значение HASH(0x…), то есть первую итерацию блока block.
+
$template->vars({
 +
    var => 'value',
 +
    ...
 +
});
  
<pre><!-- BEGIN block --></pre>
+
# Выполнения полностью аналогичны PHP:
 +
$page = $template->parse('имя_файла.tpl' [, { "ключ" => "значение", ... }]);
 +
$page = $template->exec_from('имя_файла.tpl', 'имя_блока' [, { "ключ" => "значение", ... }]);
 +
$page = $template->parse_inline('код' [, { "ключ" => "значение", ... }]);
 +
$page = $template->exec_from_inline('код', 'имя_блока' [, { "ключ" => "значение", ... }]);
  
Теперь, внутри блока {block} теперь будет иметь значение HASH(0x…), то есть уже значение текущей итерации блока block, а {block.#} будет иметь значением номер текущей итерации блока, отсчитываемый с 0, а не с 1, как в старой версии.
+
# Очистка сохранённых данных для генерации ещё одной страницы:
 +
$template->clear;
 +
</source>
  
<pre><!-- END block --></pre>
+
== Синтаксис шаблонов ==
  
На <tt><nowiki><!-- END другоеимя --></nowiki></tt> после <tt><nowiki><!-- BEGIN block --></nowiki></tt> при <tt>strict_end = true</tt> шаблонизатор выдаст ошибку, «ибо нефиг» (c). Если block в хеше данных — не массив, а хеш — это значит, что итерация у блока только одна, и тогда <tt><nowiki><!-- BEGIN block --></nowiki></tt> работает <s>как for($expression) {} в Perl</s> никак.
+
Шаблон — любой текст (типично — HTML), в который ''местами'' включены директивы и/или подстановки.
  
<tt>BEGIN ... END</tt> — это циклы в «старом стиле». А можно использовать и TT-подобный, обычно более удобный:
+
Если вы знаете о порождающих грамматиках, то вот контекстно-свободная грамматика для LALR(1) алгоритма разбора:
 +
 
 +
* Голая Bison-грамматика, для получения представления о синтаксисе: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.y
 +
* Рабочая [http://github.com/vitalif/lime LIME]-грамматика: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.lime (для её работы нужен патченый LIME — см. https://github.com/vitalif/lime)
 +
* Рабочая {{CPAN|Parse::Yapp}}-грамматика: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.yp
 +
 
 +
=== Пример ===
  
 
<pre>
 
<pre>
<!-- FOR var = expression -->
+
<!-- SET title -->
...
+
    Статистика
 +
<!-- END -->
 +
<!-- SET headscripts -->
 +
    <script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jquery.min.js"></script>
 +
<!-- END -->
 +
<!-- INCLUDE "admin_header.tpl" -->
 +
<!-- IF NOT srcid -->
 +
    <p>Добро пожаловать в простую OLAPообразную статистику. Выберите источник данных:</p>
 +
    <form action="?" method="GET">
 +
        <select style="width:100px" name="datasource">
 +
            <!-- FOR s = sources -->
 +
            <option value="{s s.id}">{s s.name}</option>
 +
            <!-- END -->
 +
        </select>
 +
        <input type="submit" value=" Продолжить " />
 +
    </form>
 +
<!-- ELSEIF srcid == "test" || sources[srcid].mode == 'test' -->
 +
    <p>Тестовый режим.</p>
 
<!-- END -->
 
<!-- END -->
 +
<!-- INCLUDE "admin_footer.tpl" -->
 
</pre>
 
</pre>
  
Причём, если <tt>expression ::= block</tt>, то <tt>var</tt> может быть само <tt>block</tt>'ом. Это, по сути, и есть то, что делает BEGIN: <tt><nowiki><!-- BEGIN block --></nowiki></tt> эквивалентно <tt><nowiki><!-- FOR block = block --></nowiki></tt>. Предыдущее значение переменной цикла после выхода из цикла всегда восстанавливается.
+
=== Маркеры ===
  
К номеру итерации можно обратиться через '''<tt>{var#}</tt>'''.
+
* <tt><nowiki>'<!--', '-->'</nowiki></tt> — маркеры начала и конца директивы
 +
* <tt><nowiki>'{', '}'</nowiki></tt> — маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т. п.)
 +
* Подстановки можно использовать и в директивах
 +
* Маркеры начала и конца директивы можно заменить другими — если, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>. Главное, чтобы маркер начала не был равен маркеру конца.
 +
* Маркеры подстановок можно тоже заменить на другие, либо отключить вовсе.
 +
 
 +
=== Выражения ===
 +
 
 +
* Выражения состоят из переменных, операторов и вызовов функций и методов объектов
 +
* Синтаксис обращений к переменным JS-подобный: <tt>hash.key</tt>, <tt>array[0]</tt>, <tt>hash['key']</tt>
 +
* Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) — '''регистронезависимы'''
 +
* Никаких ошибок при обращениях к необъявленным переменным не происходит
 +
* Компилируемые функции: <tt>function(arg1, arg2, ...)</tt> или <tt>function single_arg</tt> (через пробел с одним аргументом)
 +
* Функции, определённые через FUNCTION: <tt>fn_name('arg' => 'value', 'arg2' => 'value2', ...)</tt> или <tt>exec('fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>
 +
* Включение других шаблонов: <tt>INCLUDE 'template.tpl'</tt>, <tt>INCLUDE('template.tpl', { 'arg' => 'value', ... })</tt>
 +
* Выполнение функции из другого шаблона: <tt>exec_from('template.tpl', 'fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>
 +
* Методы объектов: <tt>var.method(arg1, arg2, ...)</tt>
 +
 
 +
=== Операторы ===
 +
 
 +
Все операторы, кроме сравнений, левоассоциативны. Сравнения — не ассоциативны.
 +
 
 +
<tab sep="bar" class="wikitable">
 +
a .. b | Конкатенация
 +
a <html>&#x7C;&#x7C;</html> b, a OR b | Логическое ИЛИ. JS- или Perl-подобное — возвращает первое истинное значение.
 +
a XOR b | XOR — логическое исключающее ИЛИ
 +
a && b, a AND b | Логическое И
 +
a & b | Побитовое И
 +
a == b, a != b, a < b, a > b, a <= b, a >= b | Операторы сравнения
 +
a+b, a-b, a*b, a/b, a%b | Сложение, вычитание, умножение, деление, взятие остатка от деления
 +
!a, NOT a | Логическое НЕ
 +
(exp) | Выражение в скобках
 +
{ 'a' => 'b', … } | Создание хешрефа (Perl), ассоциативного массива (PHP)
 +
</tab>
 +
 
 +
=== Директивы ===
 +
 
 +
<tab sep="bar" class="wikitable">
 +
<tt>&lt;!--# Комментарий --&gt;</tt> | Комментарий
 +
<tt>&lt;!-- FOR item = array --&gt;</tt><br />…код…<br /><tt>&lt;!-- END --&gt;</tt> | Цикл. Вместо FOR можно использовать слово FOREACH. Внутри цикла можно обратиться к счётчику через <tt>{item_index}</tt>
 +
<tt>&lt;!-- IF выражение --&gt;</tt><br />…код… | Если (выражение)
 +
<tt>&lt;!-- ELSEIF выражение --&gt;</tt><br />…код… | Иначе если (выражение)
 +
<tt>&lt;!-- ELSE --&gt;</tt><br />…код… | Иначе
 +
<tt>&lt;!-- END --&gt;</tt> | Конец если / цикла / присваивания
 +
<tt>&lt;!-- SET var = выражение --&gt;</tt> | Присваивание переменной var результата выполнения выражения
 +
<tt>&lt;!-- SET var --&gt;</tt><br />…код…<br /><tt>&lt;!-- END --&gt;</tt> | Присваивание переменной var результата выполнения кода
 +
<tt>&lt;!-- FUNCTION name (arg1, arg2) = выражение --&gt;</tt> | Определение функции шаблона как результата выполнения выражения
 +
<tt>&lt;!-- FUNCTION name (arg1, arg2) --&gt;</tt><br />…код…<br /><tt>&lt;!-- END --&gt;</tt> | Определение функции / «блока» шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO
 +
</tab>
  
 
=== Функции ===
 
=== Функции ===
  
'''Операторов нет''', фильтров нет, есть функции. Пример:
+
Синтаксис вызова функций:
 +
* <tt>ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...)</tt>
 +
* <tt>ФУНКЦИЯ <пробел> ОДИН_АРГУМЕНТ</tt>
  
<pre><!-- IF OR(function(block.key1),AND(block.key2,block.key3)) --></pre>
+
Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций.
  
Почему? Тут всё просто — основываясь на предположении, что длинные выражения в шаблонах нужны очень редко, было лениво писать нормальную грамматику для разбора обычных выражений. Почему они нужны редко? Да просто минимум логики в шаблонах — признак хороших шаблонов. А функции покрывают сразу и выражения, и «фильтры», и методы объектов.
+
Расширяемость в области функций:
  
Синтаксис вызова функции нескольких аргументов:
+
;Run-time функции: В качестве функции можно использовать метод переданного в хеше данных объекта. В «функцию» можно вынести и блок кода из шаблона — см. [[#Блоки]]. Оно хорошо кэшируется.
 +
;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', и должны возвращать '''код для вычисления результата'''. То есть, они выполняются на этапе компиляции.
  
<pre><!-- function(block.key, 0, "abc") --></pre>
+
{{note}} Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:
  
Подстановка:
+
* S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.
 +
* H() — удаляет все HTML-теги, кроме «безопасных».
 +
* T() — удаляет все HTML-теги.
 +
* Q() — это addslashes(), экранирует строки для использования, например, в JS.
 +
* I() — преобразует значение к целому числу.
  
<pre>{function(block.key, 0, "abc")}</pre>
+
==== HTML-безопасный режим ====
  
Синтаксис вызова функции одного аргумента:
+
Если вы хотите использовать «HTML-безопасный» режим с автоматическим экранированием — установите опцию '''auto_escape''' равной, например, как раз «s». Смысл режима в том, чтобы:
 +
* Не экранировать все значения руками
 +
* Случайно не забыть что-то экранировать, экранируя это руками
  
<pre>
+
Работает так: если какое-то подставляемое значение не экранировано вами явно через одну из функций вроде перечисленных выше (s/t/h/i и т. п.), то оно будет экранировано функцией, заданной в auto_escape. Получается «авто-защита» от атак типа XSS. Значения, которые надо подставить «как есть», нужно предварить вызовом функции RAW: {raw value}. Тогда значение auto_escape’ом экранировано не будет.
<!-- function(block.key) -->
+
<!-- function block.key -->
+
{block.key/s}
+
{s block.key}
+
</pre>
+
  
Синтаксис вызова метода объекта:
+
На заметку: для удобства функции JSON, QUOTE, SQL_QUOTE и REQUOTE считаются «безопасными», хотя таковыми, строго говоря, не являются. Однако используются они обычно внутри JS-кода, поэтому лучше их вывод не трогать.
  
<pre>
+
=== Числа, логические операции ===
{object.method()}
+
{object.method(arg1, arg2)}
+
{call(object, "method")}
+
{call(object, "method", array(arg1, arg2))}
+
</pre>
+
  
Последние два применения — как нетрудно заметить, обращение к функции call() и служат для вызова метода по вычисляемому имени.
+
==== LOG ====
  
Цепочки вызовов методов типа <tt>object.method().another_method()</tt> не поддерживаются, ибо к ним без сохранения звеньев нервно относится даже сам PHP.
+
Логарифм.
  
=== IF ===
+
==== EVEN, ODD ====
  
Условный вывод:
+
Истина в случае, если аргумент чётный или нечётный соответственно.
  
<pre>
+
==== INT=I=INTVAL ====
<!-- IF function(block.key) --><!-- ELSEIF ... --><!-- END -->
+
<!-- IF NOT block.key -->...<!-- END -->                     
+
</pre>
+
  
ELSIF эквивалентно ELSE IF и ELSEIF.
+
Преобразование к целому числу.
  
=== SET ===
+
==== SEQ, SNE, SGT, SLT, SGE, SLE ====
  
Запись значения переменной:
+
Строковые сравнения.
  
<pre>
+
==== NEQ, NNE, NGT, NLT, NGE, NLE ====
<!-- SET block.key -->...<!-- END --></nowiki>
+
<!-- SET block.key = выражение -->
+
</pre>
+
  
=== Включения ===
+
Численные сравнения.
  
Включение другого шаблона также осталось:
+
==== YESNO ====
  
<pre><!-- INCLUDE another-file.tpl -->
+
YESNO($1, $2, $3) — тернарный оператор ($1 ? $2 : $3).
<!-- INCLUDE "another-file.tpl" --></pre>
+
  
По «динамическому» имени шаблона включение производится функцией <tt>INCLUDE</tt> (она же <tt>PARSE</tt>). Как несложно заметить, вторая строка — как раз вызов функции.
+
=== Строки ===
  
=== Блоки ===
+
==== LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE ====
  
Блок — это часть шаблона, выделенная в отдельную «функцию», хорошо кэшируемая и предназначенная для повторного вызова из других мест. Покрывает сразу несколько вещей — «блоки», «макросы» и «обёртки» из TT. Да-да, TT славится бессмысленным дублированием функционала.
+
Нижний и верхний регистр.
  
Но имеет несколько преимуществ:
+
==== LCFIRST, UCFIRST ====
* блоки, определённые в одном шаблоне, можно смело вызывать из других по имени файла + имени блока!
+
* блок можно определить просто как некоторое выражение.
+
* блоки хорошо кэшируются — с VMX::Template вы не испытаете разочарования, если вызовете какой-нибудь блок 1000 раз. В отличие от TT.
+
  
Блоки в шаблоне не могут быть вложенными, а циклы, SET и прочие вещи, их оборачивающие, не имеют на них никакого влияния. После компиляции блоки просто вырезаются и преобразуются в отдельные функции PHP/Perl’а.
+
Преобразование первого символа строки в нижний и верхний регистр соответственно.
  
<pre>
+
==== Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE ====
<!-- BLOCK имя_блока -->
+
...код...
+
<!-- END -->
+
</pre>
+
  
или
+
* Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).
 +
* Экранирование символа " удвоением — sql_quote(строка). (актуально также для [[rupedia:CSV|CSV]])
 +
* Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]).
  
<pre><!-- BLOCK имя_блока = выражение --></pre>
+
==== URI_QUOTE=URIQUOTE=URLENCODE ====
  
Вместо слова <tt>BLOCK</tt> можно также использовать слово <tt>FUNCTION</tt> или <tt>MACRO</tt>.
+
URL-кодирование строки ({{CPAN|URI::Escape}} в Perl и [http://php.net/manual/en/function.urlencode.php urlencode()] в PHP).
  
Вызывать блок из шаблона следует с помощью функции [[#INCLUDE=PROCESS=PARSE|PROCESS]]. Вызывать блок из кода следует, передавая после имени файла шаблона имя блока. См. [[#Использование (PHP)]].
+
==== REPLACE, STR_REPLACE ====
  
== Функции ==
+
* Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).
 +
* Замена подстроки в строке — str_replace(искомое, замена, строка).
  
{{note}} Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:
+
==== STRLEN ====
  
* S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.
+
Длина строки ''в символах''.
* H() — удаляет все HTML-теги, кроме «безопасных».
+
* T() — удаляет все HTML-теги.
+
* Q() — это addslashes(), экранирует символы для использования, например, в JS.
+
* I() — преобразует значение к целому числу.
+
  
Расширяемость в области функций:
+
==== SUBSTR=SUBSTRING ====
  
;Run-time функции: В качестве функции можно использовать переданный в хеше данных coderef (замыкание, ссылку на функцию, или любое «[http://php.net/manual/en/function.is-callable.php is_callable]» в случае PHP). Если же хочется вынести в «функцию» блок кода из шаблона — проще создать отдельный шаблон и вызывать его, предварительно делая <tt>&lt;!-- SET --></tt> именованных аргументов. Это действительно лучше, так как позволяет оптимально работать кэшированию.
+
Стандартная (для всех, кроме жавистов) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.
;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', и должны возвращать '''код для вычисления результата'''. То есть, они выполняются на этапе компиляции.
+
  
=== OR, AND, NOT ===
+
==== TRIM ====
  
Логические ИЛИ, И, НЕ, действующие аналогично Perl операторам ||, &&, !.
+
Удаление пробелов из начала и конца строки.
  
=== EVEN, ODD ===
+
==== SPLIT ====
  
Истина в случае, если аргумент чётный или нечётный соответственно.
+
Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split])
  
=== INT=I, ADD, MUL, DIV, MOD, LOG ===
+
==== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ====
  
Преобразование к целому числу, арифметические операции и логарифм.
+
* Преобразование символов < > & " ' в HTML-сущности.
 +
* Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов.
 +
* Удаление только «небезопасных» HTML-тегов.
 +
* Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>.
  
=== EQ, NE, SEQ, SNE, GT, LT, GE, LE, SGT, SLT, SGE, SLE ===
+
==== CONCAT, JOIN=IMPLODE ====
  
Действуют аналогично Perl операторам == eq > < >= <= gt lt ge le.
+
* Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.
 +
* Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.
  
В PHP-версии на данный момент такого много-безобразия нет, есть просто EQ NE GT LT GE LE. Хотя это и имеет свои минусы - если хотя бы один аргумент принимается PHP как численный, сравнение становится численным.
+
==== SUBST, SPRINTF, STRFTIME ====
  
=== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ===
+
Subst — подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …).
  
Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).
+
Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf].
  
Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.
+
Форматирование даты и/или времени с помощью функции [http://www.manpagez.com/man/3/strftime/ strftime] — strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:
 +
* UNIX время.
 +
* Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — опционально временная зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря, <pre>^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$</pre>
 +
* Оракловский формат даты-времени: <nowiki>ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС</nowiki>.
 +
* RFC 822.
  
Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).
+
==== STRLIMIT=TRUNCATE ====
  
=== ARRAY, HASH ===
+
Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> — <tt>strlimit(str, len, dots = "...")</tt>. Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется <tt>dots</tt> или по умолчанию <tt>"..."</tt>, если аргумент <tt>dots</tt> не передаётся.
  
Создание массива или хэша из всех атрибутов.
+
==== PLURAL_RU ====
  
Соответственно в хеше атрибуты идут парами КЛЮЧ, ЗНАЧЕНИЕ, КЛЮЧ, ЗНАЧЕНИЕ и т.п. (специального синтаксиса "=>" нет).
+
Выбор правильного окончания в русском языке в зависимости от количества: <tt>plural_ru(число, один, несколько, много)</tt>. Например (1 шаблон, 2-3-4-102 шаблонА, 5-6-15-… шаблонОВ):
  
=== SORT ===
+
<tt><nowiki>{num} шаблон{plural_ru(num, '', 'а', 'ов')}</nowiki></tt>
 +
 
 +
=== Массивы и хеши ===
 +
 
 +
==== HASH ====
 +
 
 +
Создание хэша из всех аргументов.
 +
 
 +
Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и т. п.
 +
 
 +
==== KEYS, HASH_KEYS, ARRAY_KEYS ====
 +
 
 +
Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — нет.
 +
 
 +
==== SORT ====
  
 
Сортировка массива по значениям.
 
Сортировка массива по значениям.
  
=== ARRAY_MERGE ===
+
==== PAIRS ====
  
Слить массивы в один. Под Perl — только массивы (не хеши), под PHP — любые массивы.
+
Массив хэшей вида <tt>{ key => ключ, value => значение }</tt> для хэша, в случае Perl ключи будут отсортированы по имени.
  
=== GET, AGET, HGET ===
+
==== ARRAY, RANGE ====
  
Получение элемента массива/хэша по «динамическому» ключу. По-моему, это лучше, чем зюки-хрюки Template Toolkit’а: <tt>hash.${hash2.$key}</tt> и т. п.
+
* Создание массива.
 +
* Диапазон от A до B — range(A, B).
  
<tt>GET(откуда, что)</tt> автоматически решает, «откуда» — это массив или хеш, AGET служит только для массивов, а HGET только для хешей. В PHP-версии все три идентичны.
+
==== IS_ARRAY ====
  
<tt>GET(что)</tt> — получение значения переменной верхнего уровня.
+
Проверка, является ли аргумент массивом. В PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко.
  
=== MAP ===
+
==== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ====
  
Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы).
+
* Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).
 +
* Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.
 +
* Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).
  
=== LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE ===
+
==== GET ====
  
Нижний и верхний регистр.
+
<tt>GET(что)</tt> — получение значения переменной верхнего уровня.
  
=== STRLIMIT ===
+
==== SET ====
  
Ограничение длины строки s максимальной длиной l — strlimit(s, l). Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется «…» (троеточие).
+
<tt>SET(куда, что)</tt> — присваивание «куда» значения «что». Уравнения, понятное дело, не решает, то есть, как и обычно, присваивать можно только lvalue :)
  
=== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ===
+
==== ARRAY_MERGE ====
  
Преобразование символов < > & " ' в HTML-сущности.
+
Слить массивы в один. Под Perl — только массивы (не хеши), под PHP — любые массивы.
  
Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов.
+
==== SHIFT, POP, UNSHIFT, PUSH ====
  
Удаление только запрещённых тегов.
+
* Вынуть элемент из начала массива — <tt>shift(array)</tt>
 +
* Вынуть из конца — <tt>pop(array)</tt>
 +
* Добавить в начало — <tt>unshift(array, value)</tt>
 +
* Добавить в конец — <tt>push(array, value)</tt>.
  
Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>.
+
=== Включения ===
  
=== URI_QUOTE=URIQUOTE=URLENCODE ===
+
Сюда относятся функции выполнения других шаблонов и/или их блоков. Во все эти функции можно передавать «данные» (tpldata) либо с помощью создания хеша функцией hash(), либо просто передачей аргументов как <tt>КЛЮЧ => ЗНАЧЕНИЕ, ...</tt>.
  
URL-кодирование строки ({{CPAN|URI::Escape}} в Perl и [http://php.net/manual/en/function.urlencode.php urlencode()] в PHP).
+
==== PARSE=INCLUDE=PROCESS ====
  
=== CONCAT, JOIN=IMPLODE ===
+
Включение другого шаблона.
  
Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.
+
<pre>
 +
parse('имя файла')
 +
parse('имя файла', hash( ключ => значение, ... ))
 +
parse('имя файла', ключ => значение, ...)
 +
</pre>
  
Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.
+
==== PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE ====
  
=== SUBSTR=SUBSTRING, STRLEN ===
+
Включение кода не из файла, а просто из строки — <tt>parse_inline('код шаблона'[, аргументы])</tt>.
  
Стандартная (для всех кроме жавистов стандартная) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.
+
==== EXEC ====
  
Ну и функция «длина строки».
+
Включение блока из текущего шаблона — <tt>exec('имя блока'[, аргументы])</tt>.
  
=== Q=QUOTE=ADDSLASHES, REQUOTE=RE_QUOTE=PREG_QUOTE ===
+
==== EXEC_FROM ====
  
Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).
+
Включение блока из другого шаблона — <tt>exec_from('имя файла', 'имя блока'[, аргументы])</tt>.
  
Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]).
+
==== EXEC_FROM_INLINE ====
  
=== REPLACE, SPLIT ===
+
Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — <tt>exec_from_inline('код шаблона', 'имя блока'[, аргументы])</tt>.
  
Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).
+
=== Прочее ===
  
Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split])
+
==== VOID ====
  
=== DUMP, JSON ===
+
Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток.
 +
 
 +
==== RAW ====
 +
 
 +
Пустое преобразование первого аргумента со снятием флага «небезопасности». Нужно для подстановки значений «как есть» в HTML-безопасном режиме авто-экранирования.
 +
 
 +
==== DUMP=VAR_DUMP ====
  
 
Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP.
 
Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP.
  
Форматирование структуры данных в формат JSON.
+
==== JSON ====
  
=== INCLUDE=PROCESS=PARSE ===
+
Форматирование любой структуры данных в формат JSON.
  
Включение другого шаблона или выполнение блока.
+
==== CALL ====
  
<pre>
+
Вызов метода объекта по «динамическому» имени — <tt>call(varref, method_name, arg1, arg2, arg3, ...)</tt>.
process('имя файла')
+
process('имя файла', 'имя блока')
+
process('имя файла', 'имя блока', hash( аргументы ))
+
process('::имя блока в текущем шаблоне' [, hash(аргументы)])
+
</pre>
+
  
Не рекомендуется, но возможно также и передавать код вместо имени файла:
+
==== MAP ====
  
<pre>
+
Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы).
process('', 'код шаблона' [, 'функция'] [, hash( аргументы )])
+
</pre>
+
  
=== SUBST, SPRINTF, STRFTIME ===
+
== Изменения относительно старых версий ==
  
Подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, ).
+
* Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() — теперь, как и обычно, передаётся просто хеш с данными $vars
 +
* Синтаксис типа {a->key} ликвидирован
 +
* Авто-переводы, которые были в перловой версии — тоже тю-тю (хотя, может, и будут возрождены)
 +
* SET теперь заканчивается обычным END, а не ENDSET
 +
* Обращение к счётчику цикла теперь {block_index}, а не {block.#}
 +
* Добавлены функции, операторы
 +
* Добавлены детальные сообщения об ошибках
 +
* Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона
  
Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf].
+
== Различия PHP и Perl версий ==
  
Форматирование даты и/или времени с помощью функции [http://www.manpagez.com/man/3/strftime/ strftime] — strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:
+
==== Кэширование работает по-разному ====
* UNIX время.
+
 
* Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — опционально временная зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря, <pre>^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$</pre>
+
В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая:
* Оракловский формат даты-времени: <nowiki>ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС</nowiki>.
+
 
* RFC 822.
+
* Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl</tt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит. Иными словами, ''никто больше не использует CGI''. Таким образом, мы легко можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами. Так и живём — скомпилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в ''экземпляре объекта VMXTemplate'' и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода. '''Важное следствие:''' объект VMXTemplate между запросами нужно оставлять живым. Если его убить — кэш полностью очищается.
 +
* PHP: интерпретатор PHP всегда инициализируется заново при обработке каждого HTTP-запроса, а живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно. Однако предполагается, что всё прогрессивное человечество давно использует APC/XCache/ZendOpCache/eAccelerator, и поэтому, когда текст шаблонов компилируется в файлы, а файлы подгружаются путём require, на самом деле они загружаются не с диска, а из памяти кэшера, причём — в уже скомпилированном виде. Кроме того, так как скомпилированный шаблон представляет собой класс — в рамках одного запроса он загружается максимум 1 раз, последующие вызовы происходят уже очень быстро. Ну и на всякий пожарный — хотя это, возможно, уже особого выигрыша и не даёт — нескомпилированный текст шаблонов тоже кэшируется в кэше переменных APC/XCache/eAccelerator, если таковой присутствует, и не перезагружается с диска лишний раз. Если <tt>reload = false</tt>, лишними считаются все разы, кроме первого, даже если файл шаблона менялся.
 +
 
 +
В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как <tt>reload = false</tt> в PHP, но если <tt>reload > 0</tt>, то тексты шаблонов всё-таки перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд.
 +
 
 +
В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они дёргают разные функции от $this, подразумевая, что это объект класса VMXTemplate.
 +
 
 +
Поэтому компилированный шаблон PHP-версии — это класс, производный от класса VMXTemplate. Как уже сказано выше, единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий функции шаблона (&lt;!-- FUNCTION … --&gt;).
 +
 
 +
==== Несколько различается действие <tt>use_utf8 = true</tt> ====
 +
 
 +
* Общий смысл — «мои шаблоны и страницы в кодировке UTF-8».
 +
* PHP: «использовать mb_str* функции для работы со строками в выражениях».
 +
* Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.
 +
*: Для приведения всех переменных шаблона к UTF-8 можно использовать функцию <tt>utf8on()</tt> из <tt>VMXTemplate::Utils</tt> (рекурсивный <tt>Encode::_utf8_on()</tt>).
 +
 
 +
==== Различается способ вывода ошибок при <tt>print_error = true</tt> ====
 +
 
 +
* Общий смысл — при <tt>print_error = true</tt> ошибки и предупреждения должны попасть на экран.
 +
* PHP: ошибки группируются и выводятся в отдельном div’е в конце страницы, либо просто print’ами по месту возникновения при вызове из консоли.
 +
* Perl: текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом <tt>parse()</tt>).
 +
 
 +
==== Различается поведение сравнений ====
 +
 
 +
* PHP: Тип обычных операторов сравнения определяется во время выполнения. То есть, если во время выполнения одно из сравниваемых значений — число, они сравниваются как числа, иначе — как строки.
 +
* Perl: Тип обычных операторов сравнения определяется ''во время компиляции''. То есть, если из контекста понятно, что одно из сравниваемых значений — число (если это константа или результат, например, функции count), сравнение будет численным, иначе — строковым.
 +
* Пустые массивы и хеши ложны в PHP и истинны в Perl. То есть простая проверка «IF array» (приведение к булеву типу), если array пуст, в PHP вернёт false, а в Perl — true.
 +
 
 +
==== Различается поведение некоторых функций работы с массивами и хешами ====
 +
 
 +
* KEYS — в PHP порядок ключей массива/хеша сохраняется, а в Perl — нет и принимаются только хеши. Обусловлено реализацией хешей в этих языках.
 +
* PAIRS — в PHP порядок ключей сохраняется, в Perl-версии ключи будут отсортированы по имени.
 +
* RANGE — в Perl-версии принимает буквенные аргументы (A..Z = весь алфавит).
 +
* IS_ARRAY — в PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко (надо проверить, численные ли все ключи).
 +
* AGET и HGET в PHP идентичны GET.
 +
* ARRAY_MERGE: под Perl — только массивы (не хеши), под PHP — любые массивы.
 +
* DUMP — это Dumper в Perl’е и var_dump в PHP.
 +
 
 +
==== Строка исходного файла ====
 +
 
 +
В PHP-версии в шаблоны не включаются C-подобные «прагмы» #line, а в текст ошибок не включается имя файла шаблона и строка. Ибо решил — раз уж #line не поддерживается, нечего на строки заморачиваться.
 +
 
 +
== А кстати, зачем вообще нужен шаблонизатор? ==
 +
 
 +
Ответы:
 +
* Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.
 +
* Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это приблизительно называется MVP (Model-View-Presenter; View имеет связь с моделью только через Presenter) и сразу же ликвидирует:
 +
** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.
 +
** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.
 +
* Второй вариант — классический MVC, шаблон — это View (представление), во View передаётся модель, и View отображает состояние модели так, как ему хочется. То есть, шаблон общается напрямую с живыми объектами модели, которые ему передают, «обратная связь» присутствует.
 +
*: {{Warning}} Важное ИМХО! Частая проблема классического подхода — по 100 запросов для чтения одного и того же свойства при отображении 100 объектов выборки, вместо того, чтобы прочитать это свойство за 1 запрос сразу для всех 100 объектов. Чтобы такого не было — нужно, чтобы объекты помнили, частью какой коллекции они являются, и при чтении свойства читали его сразу для всех объектов «своей» коллекции. Идея основана на предположении, что если у объекта, прочитанного из БД как часть большой выборки, запрашивается какое-то свойство — велика вероятность того, что это же свойство будет запрошено и у всех остальных объектов той же самой выборки. При такой реализации — и писать удобно (не нужно заморачиваться, что передавать в шаблон, а что нет), и производительность не страдает.
 +
 
 +
Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:
 +
* ''Руками'' писать в «шаблонном стиле»:
 +
*# Не смешивать сложные конструкции с HTML.
 +
*# Шаблоны выносить в отдельные функции и общаться с ними через 1 ассоциативный массив с данными.
 +
*# Вывод не печатать, а буферизовать (ob_start() &rarr; ob_get_contents() &rarr; ob_end_clean()) и возвращать.
 +
* Использовать читаемый стиль кода (?> и <?php только в конце строк, отступы насквозь через PHP и HTML).
 +
Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость.
  
[[Категория:Sway]]
+
[[Категория:Техактивы]]
[[Категория:Разработка]]
+
 
[[Категория:Perl]]
 
[[Категория:Perl]]
 +
[[Категория:PHP]]

Текущая версия на 23:43, 27 июля 2018

VMX::Template — простой и высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии.

  • Лицензия: GNU GPL версии 3 или новее
  • Полный набор исходников здесь: http://yourcmc.ru/git/vitalif/VMXTemplate
  • Простые настройки для подсветки синтаксиса шаблонов в Midnight Commander'а: tpl.syntax. Чтобы подсветка нормально выглядела, к tpl.syntax в начало надо дописать html.syntax из стандартного комплекта поставки mc.

Содержание

Что это за шаблонизатор?

VMX::Template изначально реализован по мотивам примитивного шаблонизатора, взятого из кода форума phpBB 2 (историческое описание старой версии), и с тех пор (с 2006 года) переписанного 4 раза:

  1. На регулярные выражения →
  2. На поиск подстроки →
  3. На метод рекурсивного спуска →
  4. И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME

Есть PHP и Perl версии шаблонизатора, основная версия шаблонизатора — в данный момент PHP. Есть некоторые различия реализации — например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь XCache или eAccelerator, ибо там сохранить coderef между запросами невозможно.

Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается».

Это к тому, что и идея, и синтаксис шаблонов вообще-то схожи, но сама реализация гораздо проще и быстрее.

Использование

PHP

Требуется PHP версии не ниже 5.4.

require_once 'template.php';
 
# Конструктор (значения опций - по умолчанию)
$template = new VMXTemplate(array(
    'root'          => '.',        # директория с шаблонами
    'cache_dir'     => './cache',  # директория для кэширования компилированного кода шаблонов
    'print_error'   => false,      # если true, ошибки компиляции выводятся на STDOUT
    'raise_error'   => false,      # если true, при ошибке компиляции вызывается die()
    'log_error'     => false,      # если true, ошибки компиляции логгируются через error_log()
    'reload'        => true,       # если false, шаблоны будут считываться с диска только 1 раз, и вызовов stat() происходить не будет
    'use_utf8'      => true,       # если true, использовать кодировку UTF-8 для строковых операций
    'begin_code'    => '<!--',     # маркер начала директивы кода
    'end_code'      => '-->',      # маркер конца директивы кода
    'eat_code_line' => true,       # (похоже на TT CHOMP) съедать "лишний" перевод строки, если в строке только директива?
    'begin_subst'   => '{',        # маркер начала подстановки выражения (либо {, либо false)
    'end_subst'     => '}',        # маркер конца подстановки выражения (либо }, либо false)
    'compiletime_functions' =>     # дополнительные компилируемые функции
        array('func' => callback), # хеш вида имя функции (в шаблонах) => callback($template, array $args),
                                   # которому передаются скомпилированные выражения всех аргументов
    'strip_space'   => true,       # встроенный фильтр: срезание пробелов из начал и концов строк
    'filters'       =>             # фильтры - выполняются над выводом всех шаблонов
        array(callback1, ...),
    'auto_escape'   => false,      # функция авто-экранирования, например "s" (для HTML-безопасного режима)
));
 
# Присвоение одной переменной:
$template->vars("ключ", "значение");
 
# Присвоение кучи переменных:
$template->vars(array("ключ" => "значение", ...));
 
# Выполнение шаблона и получение результата:
# (возможно с передачей данных в шаблон)
$page = $template->parse('имя_файла.tpl' [, array("ключ" => "значение", ...)]);
 
# Выполнение именованного блока из файла:
$page = $template->exec_from('имя_файла.tpl', 'имя_блока' [, array("ключ" => "значение", ...)]);
 
# Выполнение кода из строки:
$page = $template->parse_inline('код' [, array("ключ" => "значение", ...)]);
 
# Выполнение именованного блока из кода (не рекомендуется, но возможно):
$page = $template->exec_from_inline('код', 'имя_блока' [, array("ключ" => "значение", ...)]);
 
# Очистка сохранённых данных для генерации ещё одной страницы:
$template->clear;

Perl

Perl версия обновлена и теперь в точности соответствует PHP-версии.

use VMXTemplate;
 
# Конструктор
$template = new VMXTemplate(
    'root'          => '.',        # директория с шаблонами
    'cache_dir'     => undef,      # директория для кэширования компилированного кода шаблонов
    'reload'        => 2,          # если 0, то шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
                                   # если >0, то шаблоны будут перечитываться с диска не чаще чем раз в reload секунд
    'print_error'   => 1,          # если TRUE, ошибки компиляции попадают в вывод шаблона
    'log_error'     => 1,          # если TRUE, ошибки компиляции печатаются на STDERR
    'raise_error'   => 0,          # если TRUE, при ошибке компиляции вызывается die()
    'use_utf8'      => undef,      # если TRUE, использовать "use utf8" на шаблонах
    'begin_code'    => '<!--',     # маркер начала директивы кода
    'end_code'      => '-->',      # маркер конца директивы кода
    'begin_subst'   => '{',        # маркер начала подстановки выражения
    'end_subst'     => '}',        # маркер конца подстановки выражения
    'eat_code_line' => 1,          # (похоже на TT CHOMP) если TRUE, съедать "лишний" перевод строки, если в строке только директива кода (begin_code..end_code)
    'no_code_subst' => 1,          # если TRUE, выполнять директивы кода (begin_code..end_code), но игнорировать их результат
    'compiletime_functions' =>     # дополнительные компилируемые функции
        { 'func' => sub {} },      # хеш вида имя функции (в шаблонах) => coderef,
                                   # которому передаются скомпилированные выражения всех аргументов и первым - парсер (объект VMXTemplate::Parser)
    'filters' => [ sub {}, .. ],   # фильтры для запуска на выводе каждого внешнего шаблона (фильтр - функция, модифицирующая $_[0])
    'strip_space'   => 0,          # если TRUE, удалять пробелы и табы из начала и конца всех строк вывода
    'auto_escape'   => '',         # функция авто-экранирования, например "s" (для HTML-безопасного режима)
);
 
$template->vars({
    var => 'value',
    ...
});
 
# Выполнения полностью аналогичны PHP:
$page = $template->parse('имя_файла.tpl' [, { "ключ" => "значение", ... }]);
$page = $template->exec_from('имя_файла.tpl', 'имя_блока' [, { "ключ" => "значение", ... }]);
$page = $template->parse_inline('код' [, { "ключ" => "значение", ... }]);
$page = $template->exec_from_inline('код', 'имя_блока' [, { "ключ" => "значение", ... }]);
 
# Очистка сохранённых данных для генерации ещё одной страницы:
$template->clear;

Синтаксис шаблонов

Шаблон — любой текст (типично — HTML), в который местами включены директивы и/или подстановки.

Если вы знаете о порождающих грамматиках, то вот контекстно-свободная грамматика для LALR(1) алгоритма разбора:

Пример

<!-- SET title -->
    Статистика
<!-- END -->
<!-- SET headscripts -->
    <script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jquery.min.js"></script>
<!-- END -->
<!-- INCLUDE "admin_header.tpl" -->
<!-- IF NOT srcid -->
    <p>Добро пожаловать в простую OLAPообразную статистику. Выберите источник данных:</p>
    <form action="?" method="GET">
        <select style="width:100px" name="datasource">
            <!-- FOR s = sources -->
            <option value="{s s.id}">{s s.name}</option>
            <!-- END -->
        </select>
        <input type="submit" value=" Продолжить " />
    </form>
<!-- ELSEIF srcid == "test" || sources[srcid].mode == 'test' -->
    <p>Тестовый режим.</p>
<!-- END -->
<!-- INCLUDE "admin_footer.tpl" -->

Маркеры

  • '<!--', '-->' — маркеры начала и конца директивы
  • '{', '}' — маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т. п.)
  • Подстановки можно использовать и в директивах
  • Маркеры начала и конца директивы можно заменить другими — если, например, вы привыкли к TT, можно установить [% %]. Главное, чтобы маркер начала не был равен маркеру конца.
  • Маркеры подстановок можно тоже заменить на другие, либо отключить вовсе.

Выражения

  • Выражения состоят из переменных, операторов и вызовов функций и методов объектов
  • Синтаксис обращений к переменным JS-подобный: hash.key, array[0], hash['key']
  • Имена переменных регистрозависимы, имена встроенных функций и названия директив (BEGIN, END и т. п.) — регистронезависимы
  • Никаких ошибок при обращениях к необъявленным переменным не происходит
  • Компилируемые функции: function(arg1, arg2, ...) или function single_arg (через пробел с одним аргументом)
  • Функции, определённые через FUNCTION: fn_name('arg' => 'value', 'arg2' => 'value2', ...) или exec('fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })
  • Включение других шаблонов: INCLUDE 'template.tpl', INCLUDE('template.tpl', { 'arg' => 'value', ... })
  • Выполнение функции из другого шаблона: exec_from('template.tpl', 'fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })
  • Методы объектов: var.method(arg1, arg2, ...)

Операторы

Все операторы, кроме сравнений, левоассоциативны. Сравнения — не ассоциативны.

a .. b Конкатенация
a || b, a OR b Логическое ИЛИ. JS- или Perl-подобное — возвращает первое истинное значение.
a XOR b XOR — логическое исключающее ИЛИ
a && b, a AND b Логическое И
a & b Побитовое И
a == b, a != b, a < b, a > b, a <= b, a >= b Операторы сравнения
a+b, a-b, a*b, a/b, a%b Сложение, вычитание, умножение, деление, взятие остатка от деления
!a, NOT a Логическое НЕ
(exp) Выражение в скобках
{ 'a' => 'b', … } Создание хешрефа (Perl), ассоциативного массива (PHP)

Директивы

<!--# Комментарий --> Комментарий
<!-- FOR item = array -->
…код…
<!-- END -->
Цикл. Вместо FOR можно использовать слово FOREACH. Внутри цикла можно обратиться к счётчику через {item_index}
<!-- IF выражение -->
…код…
Если (выражение)
<!-- ELSEIF выражение -->
…код…
Иначе если (выражение)
<!-- ELSE -->
…код…
Иначе
<!-- END --> Конец если / цикла / присваивания
<!-- SET var = выражение --> Присваивание переменной var результата выполнения выражения
<!-- SET var -->
…код…
<!-- END -->
Присваивание переменной var результата выполнения кода
<!-- FUNCTION name (arg1, arg2) = выражение --> Определение функции шаблона как результата выполнения выражения
<!-- FUNCTION name (arg1, arg2) -->
…код…
<!-- END -->
Определение функции / «блока» шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO

Функции

Синтаксис вызова функций:

  • ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...)
  • ФУНКЦИЯ <пробел> ОДИН_АРГУМЕНТ

Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций.

Расширяемость в области функций:

Run-time функции
В качестве функции можно использовать метод переданного в хеше данных объекта. В «функцию» можно вынести и блок кода из шаблона — см. #Блоки. Оно хорошо кэшируется.
Compile-time функции
При создании объекта шаблона можно передать параметр compiletime_functions, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными коду для вычисления соответствующего аргумента, и должны возвращать код для вычисления результата. То есть, они выполняются на этапе компиляции.

Note.svg Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:

  • S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.
  • H() — удаляет все HTML-теги, кроме «безопасных».
  • T() — удаляет все HTML-теги.
  • Q() — это addslashes(), экранирует строки для использования, например, в JS.
  • I() — преобразует значение к целому числу.

HTML-безопасный режим

Если вы хотите использовать «HTML-безопасный» режим с автоматическим экранированием — установите опцию auto_escape равной, например, как раз «s». Смысл режима в том, чтобы:

  • Не экранировать все значения руками
  • Случайно не забыть что-то экранировать, экранируя это руками

Работает так: если какое-то подставляемое значение не экранировано вами явно через одну из функций вроде перечисленных выше (s/t/h/i и т. п.), то оно будет экранировано функцией, заданной в auto_escape. Получается «авто-защита» от атак типа XSS. Значения, которые надо подставить «как есть», нужно предварить вызовом функции RAW: {raw value}. Тогда значение auto_escape’ом экранировано не будет.

На заметку: для удобства функции JSON, QUOTE, SQL_QUOTE и REQUOTE считаются «безопасными», хотя таковыми, строго говоря, не являются. Однако используются они обычно внутри JS-кода, поэтому лучше их вывод не трогать.

Числа, логические операции

LOG

Логарифм.

EVEN, ODD

Истина в случае, если аргумент чётный или нечётный соответственно.

INT=I=INTVAL

Преобразование к целому числу.

SEQ, SNE, SGT, SLT, SGE, SLE

Строковые сравнения.

NEQ, NNE, NGT, NLT, NGE, NLE

Численные сравнения.

YESNO

YESNO($1, $2, $3) — тернарный оператор ($1 ? $2 : $3).

Строки

LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE

Нижний и верхний регистр.

LCFIRST, UCFIRST

Преобразование первого символа строки в нижний и верхний регистр соответственно.

Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE

  • Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).
  • Экранирование символа " удвоением — sql_quote(строка). (актуально также для CSV)
  • Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. perldoc perlre).

URI_QUOTE=URIQUOTE=URLENCODE

URL-кодирование строки (URI::Escape в Perl и urlencode() в PHP).

REPLACE, STR_REPLACE

  • Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).
  • Замена подстроки в строке — str_replace(искомое, замена, строка).

STRLEN

Длина строки в символах.

SUBSTR=SUBSTRING

Стандартная (для всех, кроме жавистов) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.

TRIM

Удаление пробелов из начала и конца строки.

SPLIT

Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. perldoc -f split)

S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR

  • Преобразование символов < > & " ' в HTML-сущности.
  • Удаление всех HTML/XML тегов.
  • Удаление только «небезопасных» HTML-тегов.
  • Преобразование переводов строк (\n) в HTML-тег <br />.

CONCAT, JOIN=IMPLODE

  • Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.
  • Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.

SUBST, SPRINTF, STRFTIME

Subst — подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …).

Sprintf — он и в Африке sprintf.

Форматирование даты и/или времени с помощью функции strftime — strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:

  • UNIX время.
  • Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — опционально временная зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря,
    ^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$
  • Оракловский формат даты-времени: ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС.
  • RFC 822.

STRLIMIT=TRUNCATE

Ограничение длины строки str максимальной длиной len — strlimit(str, len, dots = "..."). Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется dots или по умолчанию "...", если аргумент dots не передаётся.

PLURAL_RU

Выбор правильного окончания в русском языке в зависимости от количества: plural_ru(число, один, несколько, много). Например (1 шаблон, 2-3-4-102 шаблонА, 5-6-15-… шаблонОВ):

{num} шаблон{plural_ru(num, '', 'а', 'ов')}

Массивы и хеши

HASH

Создание хэша из всех аргументов.

Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и т. п.

KEYS, HASH_KEYS, ARRAY_KEYS

Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — нет.

SORT

Сортировка массива по значениям.

PAIRS

Массив хэшей вида { key => ключ, value => значение } для хэша, в случае Perl ключи будут отсортированы по имени.

ARRAY, RANGE

  • Создание массива.
  • Диапазон от A до B — range(A, B).

IS_ARRAY

Проверка, является ли аргумент массивом. В PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко.

COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD

  • Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).
  • Аналог функции array_slice из PHP.
  • Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).

GET

GET(что) — получение значения переменной верхнего уровня.

SET

SET(куда, что) — присваивание «куда» значения «что». Уравнения, понятное дело, не решает, то есть, как и обычно, присваивать можно только lvalue :)

ARRAY_MERGE

Слить массивы в один. Под Perl — только массивы (не хеши), под PHP — любые массивы.

SHIFT, POP, UNSHIFT, PUSH

  • Вынуть элемент из начала массива — shift(array)
  • Вынуть из конца — pop(array)
  • Добавить в начало — unshift(array, value)
  • Добавить в конец — push(array, value).

Включения

Сюда относятся функции выполнения других шаблонов и/или их блоков. Во все эти функции можно передавать «данные» (tpldata) либо с помощью создания хеша функцией hash(), либо просто передачей аргументов как КЛЮЧ => ЗНАЧЕНИЕ, ....

PARSE=INCLUDE=PROCESS

Включение другого шаблона.

parse('имя файла')
parse('имя файла', hash( ключ => значение, ... ))
parse('имя файла', ключ => значение, ...)

PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE

Включение кода не из файла, а просто из строки — parse_inline('код шаблона'[, аргументы]).

EXEC

Включение блока из текущего шаблона — exec('имя блока'[, аргументы]).

EXEC_FROM

Включение блока из другого шаблона — exec_from('имя файла', 'имя блока'[, аргументы]).

EXEC_FROM_INLINE

Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — exec_from_inline('код шаблона', 'имя блока'[, аргументы]).

Прочее

VOID

Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток.

RAW

Пустое преобразование первого аргумента со снятием флага «небезопасности». Нужно для подстановки значений «как есть» в HTML-безопасном режиме авто-экранирования.

DUMP=VAR_DUMP

Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP.

JSON

Форматирование любой структуры данных в формат JSON.

CALL

Вызов метода объекта по «динамическому» имени — call(varref, method_name, arg1, arg2, arg3, ...).

MAP

Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы).

Изменения относительно старых версий

  • Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() — теперь, как и обычно, передаётся просто хеш с данными $vars
  • Синтаксис типа {a->key} ликвидирован
  • Авто-переводы, которые были в перловой версии — тоже тю-тю (хотя, может, и будут возрождены)
  • SET теперь заканчивается обычным END, а не ENDSET
  • Обращение к счётчику цикла теперь {block_index}, а не {block.#}
  • Добавлены функции, операторы
  • Добавлены детальные сообщения об ошибках
  • Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона

Различия PHP и Perl версий

Кэширование работает по-разному

В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая:

  • Perl: считается, что всё прогрессивное человечество уже давно использует mod_perl или другие способы запуска веб-приложений, при которых частых переинициализаций интерпретатора не происходит. Иными словами, никто больше не использует CGI. Таким образом, мы легко можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами. Так и живём — скомпилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в экземпляре объекта VMXTemplate и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода. Важное следствие: объект VMXTemplate между запросами нужно оставлять живым. Если его убить — кэш полностью очищается.
  • PHP: интерпретатор PHP всегда инициализируется заново при обработке каждого HTTP-запроса, а живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно. Однако предполагается, что всё прогрессивное человечество давно использует APC/XCache/ZendOpCache/eAccelerator, и поэтому, когда текст шаблонов компилируется в файлы, а файлы подгружаются путём require, на самом деле они загружаются не с диска, а из памяти кэшера, причём — в уже скомпилированном виде. Кроме того, так как скомпилированный шаблон представляет собой класс — в рамках одного запроса он загружается максимум 1 раз, последующие вызовы происходят уже очень быстро. Ну и на всякий пожарный — хотя это, возможно, уже особого выигрыша и не даёт — нескомпилированный текст шаблонов тоже кэшируется в кэше переменных APC/XCache/eAccelerator, если таковой присутствует, и не перезагружается с диска лишний раз. Если reload = false, лишними считаются все разы, кроме первого, даже если файл шаблона менялся.

В Perl действие reload немного отличается — reload = 0 работает так же, как reload = false в PHP, но если reload > 0, то тексты шаблонов всё-таки перезагружаются с диска при изменении, но не чаще, чем раз в reload секунд.

В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа classkit, а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они дёргают разные функции от $this, подразумевая, что это объект класса VMXTemplate.

Поэтому компилированный шаблон PHP-версии — это класс, производный от класса VMXTemplate. Как уже сказано выше, единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на tpldata и поле parent, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий функции шаблона (<!-- FUNCTION … -->).

Несколько различается действие use_utf8 = true

  • Общий смысл — «мои шаблоны и страницы в кодировке UTF-8».
  • PHP: «использовать mb_str* функции для работы со строками в выражениях».
  • Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.
    Для приведения всех переменных шаблона к UTF-8 можно использовать функцию utf8on() из VMXTemplate::Utils (рекурсивный Encode::_utf8_on()).

Различается способ вывода ошибок при print_error = true

  • Общий смысл — при print_error = true ошибки и предупреждения должны попасть на экран.
  • PHP: ошибки группируются и выводятся в отдельном div’е в конце страницы, либо просто print’ами по месту возникновения при вызове из консоли.
  • Perl: текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом parse()).

Различается поведение сравнений

  • PHP: Тип обычных операторов сравнения определяется во время выполнения. То есть, если во время выполнения одно из сравниваемых значений — число, они сравниваются как числа, иначе — как строки.
  • Perl: Тип обычных операторов сравнения определяется во время компиляции. То есть, если из контекста понятно, что одно из сравниваемых значений — число (если это константа или результат, например, функции count), сравнение будет численным, иначе — строковым.
  • Пустые массивы и хеши ложны в PHP и истинны в Perl. То есть простая проверка «IF array» (приведение к булеву типу), если array пуст, в PHP вернёт false, а в Perl — true.

Различается поведение некоторых функций работы с массивами и хешами

  • KEYS — в PHP порядок ключей массива/хеша сохраняется, а в Perl — нет и принимаются только хеши. Обусловлено реализацией хешей в этих языках.
  • PAIRS — в PHP порядок ключей сохраняется, в Perl-версии ключи будут отсортированы по имени.
  • RANGE — в Perl-версии принимает буквенные аргументы (A..Z = весь алфавит).
  • IS_ARRAY — в PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко (надо проверить, численные ли все ключи).
  • AGET и HGET в PHP идентичны GET.
  • ARRAY_MERGE: под Perl — только массивы (не хеши), под PHP — любые массивы.
  • DUMP — это Dumper в Perl’е и var_dump в PHP.

Строка исходного файла

В PHP-версии в шаблоны не включаются C-подобные «прагмы» #line, а в текст ошибок не включается имя файла шаблона и строка. Ибо решил — раз уж #line не поддерживается, нечего на строки заморачиваться.

А кстати, зачем вообще нужен шаблонизатор?

Ответы:

  • Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.
  • Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это приблизительно называется MVP (Model-View-Presenter; View имеет связь с моделью только через Presenter) и сразу же ликвидирует:
    • Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.
    • Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.
  • Второй вариант — классический MVC, шаблон — это View (представление), во View передаётся модель, и View отображает состояние модели так, как ему хочется. То есть, шаблон общается напрямую с живыми объектами модели, которые ему передают, «обратная связь» присутствует.
    Warning Warning: Важное ИМХО! Частая проблема классического подхода — по 100 запросов для чтения одного и того же свойства при отображении 100 объектов выборки, вместо того, чтобы прочитать это свойство за 1 запрос сразу для всех 100 объектов. Чтобы такого не было — нужно, чтобы объекты помнили, частью какой коллекции они являются, и при чтении свойства читали его сразу для всех объектов «своей» коллекции. Идея основана на предположении, что если у объекта, прочитанного из БД как часть большой выборки, запрашивается какое-то свойство — велика вероятность того, что это же свойство будет запрошено и у всех остальных объектов той же самой выборки. При такой реализации — и писать удобно (не нужно заморачиваться, что передавать в шаблон, а что нет), и производительность не страдает.

Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:

  • Руками писать в «шаблонном стиле»:
    1. Не смешивать сложные конструкции с HTML.
    2. Шаблоны выносить в отдельные функции и общаться с ними через 1 ассоциативный массив с данными.
    3. Вывод не печатать, а буферизовать (ob_start() → ob_get_contents() → ob_end_clean()) и возвращать.
  • Использовать читаемый стиль кода (?> и <?php только в конце строк, отступы насквозь через PHP и HTML).

Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость.