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

Материал из YourcmcWiki
Перейти к: навигация, поиск
м (Операторы)
 
(не показано 38 промежуточных версий этого же участника)
Строка 1: Строка 1:
'''VMX::Template''' — простой и высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии (основная — в данный момент PHP).
+
'''VMX::Template''' — простой и высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии.
  
 
* Лицензия: GNU GPL версии 3 или новее
 
* Лицензия: GNU GPL версии 3 или новее
* Исходники (PHP): {{SVN|vitalif/trunk/Template/}} — для работы нужны файлы template.php и template.parser.php. Исходник грамматики в [{{SVN|vitalif/trunk/Template/template.lime}} template.lime]
+
* Полный набор исходников здесь: http://yourcmc.ru/git/vitalif/VMXTemplate
* Исходники (Perl), несколько устаревшие: [{{SVN|vitaphoto/solstice/lib-sway/VMX/Template.pm}} Template.pm], [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} Common.pm]
+
** 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]. Чтобы подсветка нормально выглядела, к tpl.syntax в начало надо дописать html.syntax из стандартного комплекта поставки mc.
  
Строка 21: Строка 27:
  
 
== Использование ==
 
== Использование ==
 
Здесь можно прочитать об: использовании в PHP, использовании в Perl, различиях реализаций.
 
  
 
=== PHP ===
 
=== PHP ===
  
<source lang="php">
+
Требуется PHP версии не ниже 5.4.
 +
 
 +
<code-php>
 
require_once 'template.php';
 
require_once 'template.php';
  
# Конструктор
+
# Конструктор (значения опций - по умолчанию)
 
$template = new VMXTemplate(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'      => true,      # если true, использовать кодировку UTF-8 для строковых операций
 
     'use_utf8'      => true,      # если true, использовать кодировку UTF-8 для строковых операций
Строка 48: Строка 55:
 
     'filters'      =>            # фильтры - выполняются над выводом всех шаблонов
 
     'filters'      =>            # фильтры - выполняются над выводом всех шаблонов
 
         array(callback1, ...),
 
         array(callback1, ...),
 +
    'auto_escape'  => false,      # функция авто-экранирования, например "s" (для HTML-безопасного режима)
 
));
 
));
  
Строка 71: Строка 79:
 
# Очистка сохранённых данных для генерации ещё одной страницы:
 
# Очистка сохранённых данных для генерации ещё одной страницы:
 
$template->clear;
 
$template->clear;
</source>
+
</code-php>
  
 
=== Perl ===
 
=== Perl ===
  
[[File:Warning icon.svg|32px|link=]] '''Версия устарела! См. [http://www.yourcmc.ru/wiki/index.php?title=%D0%A8%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%82%D0%BE%D1%80_VMX::Template&oldid=6200 старую версию документации].'''
+
Perl версия обновлена и теперь в точности соответствует PHP-версии.
  
 
<source lang="perl">
 
<source lang="perl">
use VMX::Template;
+
use VMXTemplate;
  
 
# Конструктор
 
# Конструктор
$template = new VMX::Template(
+
$template = new VMXTemplate(
 
     'root'          => '.',        # директория с шаблонами
 
     'root'          => '.',        # директория с шаблонами
 
     'cache_dir'    => undef,      # директория для кэширования компилированного кода шаблонов
 
     'cache_dir'    => undef,      # директория для кэширования компилированного кода шаблонов
Строка 87: Строка 95:
 
                                   # если >0, то шаблоны будут перечитываться с диска не чаще чем раз в reload секунд
 
                                   # если >0, то шаблоны будут перечитываться с диска не чаще чем раз в reload секунд
 
     'print_error'  => 1,          # если TRUE, ошибки компиляции попадают в вывод шаблона
 
     'print_error'  => 1,          # если TRUE, ошибки компиляции попадают в вывод шаблона
 +
    'log_error'    => 1,          # если TRUE, ошибки компиляции печатаются на STDERR
 
     'raise_error'  => 0,          # если TRUE, при ошибке компиляции вызывается die()
 
     'raise_error'  => 0,          # если TRUE, при ошибке компиляции вызывается die()
 
     'use_utf8'      => undef,      # если TRUE, использовать "use utf8" на шаблонах
 
     'use_utf8'      => undef,      # если TRUE, использовать "use utf8" на шаблонах
 
     'begin_code'    => '<!--',    # маркер начала директивы кода
 
     'begin_code'    => '<!--',    # маркер начала директивы кода
 
     'end_code'      => '-->',      # маркер конца директивы кода
 
     'end_code'      => '-->',      # маркер конца директивы кода
    'eat_code_line' => 1,          # (похоже на TT CHOMP) съедать "лишний" перевод строки, если в строке только директива?
 
 
     'begin_subst'  => '{',        # маркер начала подстановки выражения
 
     'begin_subst'  => '{',        # маркер начала подстановки выражения
 
     'end_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' =>    # дополнительные компилируемые функции
 
     'compiletime_functions' =>    # дополнительные компилируемые функции
 
         { 'func' => sub {} },      # хеш вида имя функции (в шаблонах) => coderef,
 
         { 'func' => sub {} },      # хеш вида имя функции (в шаблонах) => coderef,
                                   # которому передаются скомпилированные выражения всех аргументов и первым - сам $template
+
                                   # которому передаются скомпилированные выражения всех аргументов и первым - парсер (объект VMXTemplate::Parser)
     # немного legacy, устаревшее:
+
     'filters' => [ sub {}, .. ],  # фильтры для запуска на выводе каждого внешнего шаблона (фильтр - функция, модифицирующая $_[0])
     'wrapper'       => undef,     # если coderef, через это будет пропущен вывод всех шаблонов ("глобальный фильтр")
+
     'strip_space'   => 0,         # если TRUE, удалять пробелы и табы из начала и конца всех строк вывода
     'strict_end'   => 0,         # требовать <!-- END имя_блока --> после <!-- BEGIN имя_блока -->
+
     'auto_escape'   => '',         # функция авто-экранирования, например "s" (для HTML-безопасного режима)
 
);
 
);
  
# Присвоение переменных:
+
$template->vars({
$template->vars("ключ" => "значение", "ключ" => "значение", ...);
+
    var => 'value',
 +
    ...
 +
});
  
 
# Выполнения полностью аналогичны PHP:
 
# Выполнения полностью аналогичны PHP:
Строка 121: Строка 133:
 
Если вы знаете о порождающих грамматиках, то вот контекстно-свободная грамматика для LALR(1) алгоритма разбора:
 
Если вы знаете о порождающих грамматиках, то вот контекстно-свободная грамматика для LALR(1) алгоритма разбора:
  
* Голая Bison-грамматика, для получения представления о синтаксисе: {{SVN|vitalif/trunk/Template/template.y|markup}}
+
* Голая Bison-грамматика, для получения представления о синтаксисе: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.y
* Рабочая [https://github.com/vitalif/lime LIME]-грамматика: {{SVN|vitalif/trunk/Template/template.lime|markup}} (для её работы нужен патченый LIME — см. https://github.com/vitalif/lime)
+
* Рабочая [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
  
 
=== Пример ===
 
=== Пример ===
Строка 155: Строка 168:
 
* <tt><nowiki>'{', '}'</nowiki></tt> — маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т. п.)
 
* <tt><nowiki>'{', '}'</nowiki></tt> — маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т. п.)
 
* Подстановки можно использовать и в директивах
 
* Подстановки можно использовать и в директивах
* Маркеры начала и конца директивы можно заменить другими — tсли, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>.
+
* Маркеры начала и конца директивы можно заменить другими — если, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>. Главное, чтобы маркер начала не был равен маркеру конца.
* Маркеры подстановок можно отключить, но заменить другими без правки грамматики парсера нельзя.
+
* Маркеры подстановок можно тоже заменить на другие, либо отключить вовсе.
  
 
=== Выражения ===
 
=== Выражения ===
Строка 163: Строка 176:
 
* Синтаксис обращений к переменным JS-подобный: <tt>hash.key</tt>, <tt>array[0]</tt>, <tt>hash['key']</tt>
 
* Синтаксис обращений к переменным JS-подобный: <tt>hash.key</tt>, <tt>array[0]</tt>, <tt>hash['key']</tt>
 
* Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) — '''регистронезависимы'''
 
* Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) — '''регистронезависимы'''
* Никаких ошибок при обращениям к необъявленным переменным не происходит
+
* Никаких ошибок при обращениях к необъявленным переменным не происходит
 
* Компилируемые функции: <tt>function(arg1, arg2, ...)</tt> или <tt>function single_arg</tt> (через пробел с одним аргументом)
 
* Компилируемые функции: <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>
 
* Функции, определённые через FUNCTION: <tt>fn_name('arg' => 'value', 'arg2' => 'value2', ...)</tt> или <tt>exec('fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>
Строка 204: Строка 217:
 
=== Функции ===
 
=== Функции ===
  
Функции используются в выражениях как <tt>ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...)</tt> или как <tt>ФУНКЦИЯ (пробел) ОДИН_АРГУМЕНТ</tt>. Вместо запятой ", " можно также использовать «=>», например <tt>HASH(КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ)</tt>. Синтаксической разницы между ", " и «=>» никакой нет.
+
Синтаксис вызова функций:
 +
* <tt>ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...)</tt>
 +
* <tt>ФУНКЦИЯ <пробел> ОДИН_АРГУМЕНТ</tt>
  
 
Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций.
 
Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций.
Строка 220: Строка 235:
 
* Q() — это addslashes(), экранирует строки для использования, например, в JS.
 
* Q() — это addslashes(), экранирует строки для использования, например, в JS.
 
* I() — преобразует значение к целому числу.
 
* I() — преобразует значение к целому числу.
 +
 +
==== HTML-безопасный режим ====
 +
 +
Если вы хотите использовать «HTML-безопасный» режим с автоматическим экранированием — установите опцию '''auto_escape''' равной, например, как раз «s». Смысл режима в том, чтобы:
 +
* Не экранировать все значения руками
 +
* Случайно не забыть что-то экранировать, экранируя это руками
 +
 +
Работает так: если какое-то подставляемое значение не экранировано вами явно через одну из функций вроде перечисленных выше (s/t/h/i и т. п.), то оно будет экранировано функцией, заданной в auto_escape. Получается «авто-защита» от атак типа XSS. Значения, которые надо подставить «как есть», нужно предварить вызовом функции RAW: {raw value}. Тогда значение auto_escape’ом экранировано не будет.
 +
 +
На заметку: для удобства функции JSON, QUOTE, SQL_QUOTE и REQUOTE считаются «безопасными», хотя таковыми, строго говоря, не являются. Однако используются они обычно внутри JS-кода, поэтому лучше их вывод не трогать.
  
 
=== Числа, логические операции ===
 
=== Числа, логические операции ===
Строка 315: Строка 340:
  
 
Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> — <tt>strlimit(str, len, dots = "...")</tt>. Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется <tt>dots</tt> или по умолчанию <tt>"..."</tt>, если аргумент <tt>dots</tt> не передаётся.
 
Ограничение длины строки <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-… шаблонОВ):
 +
 +
<tt><nowiki>{num} шаблон{plural_ru(num, '', 'а', 'ов')}</nowiki></tt>
  
 
=== Массивы и хеши ===
 
=== Массивы и хеши ===
Строка 405: Строка 436:
  
 
Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток.
 
Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток.
 +
 +
==== RAW ====
 +
 +
Пустое преобразование первого аргумента со снятием флага «небезопасности». Нужно для подстановки значений «как есть» в HTML-безопасном режиме авто-экранирования.
  
 
==== DUMP=VAR_DUMP ====
 
==== DUMP=VAR_DUMP ====
Строка 434: Строка 469:
  
 
== Различия PHP и Perl версий ==
 
== Различия PHP и Perl версий ==
 
==== Perl-версия — устаревшая ====
 
 
На момент 2013-04-20 Perl-версия — устаревшая. А вот актуальная для неё [http://www.yourcmc.ru/wiki/index.php?title=%D0%A8%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%82%D0%BE%D1%80_VMX::Template&oldid=6200 старая версия документации].
 
  
 
==== Кэширование работает по-разному ====
 
==== Кэширование работает по-разному ====
Строка 443: Строка 474:
 
В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая:
 
В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая:
  
* Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl</tt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит. Иными словами, ''никто больше не использует CGI''. Таким образом, мы смело можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами.
+
* Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl</tt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит. Иными словами, ''никто больше не использует CGI''. Таким образом, мы легко можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами. Так и живём — скомпилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в ''экземпляре объекта VMXTemplate'' и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода. '''Важное следствие:''' объект VMXTemplate между запросами нужно оставлять живым. Если его убить — кэш полностью очищается.
* PHP: интерпретатор PHP инициализируется заново при обработке каждого HTTP-запроса. А живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно.
+
* PHP: интерпретатор PHP всегда инициализируется заново при обработке каждого HTTP-запроса, а живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно. Однако предполагается, что всё прогрессивное человечество давно использует APC/XCache/ZendOpCache/eAccelerator, и поэтому, когда текст шаблонов компилируется в файлы, а файлы подгружаются путём require, на самом деле они загружаются не с диска, а из памяти кэшера, причём — в уже скомпилированном виде. Кроме того, так как скомпилированный шаблон представляет собой класс — в рамках одного запроса он загружается максимум 1 раз, последующие вызовы происходят уже очень быстро. Ну и на всякий пожарный — хотя это, возможно, уже особого выигрыша и не даёт — нескомпилированный текст шаблонов тоже кэшируется в кэше переменных APC/XCache/eAccelerator, если таковой присутствует, и не перезагружается с диска лишний раз. Если <tt>reload = false</tt>, лишними считаются все разы, кроме первого, даже если файл шаблона менялся.
 
+
В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они используют контекст класса Template.
+
  
Поэтому компилированный шаблон PHP-версии — это класс, производный от класса Template. Единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий функции шаблона (&lt;!-- FUNCTION ... --&gt;).
+
В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как <tt>reload = false</tt> в PHP, но если <tt>reload > 0</tt>, то тексты шаблонов всё-таки перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд.
  
Кроме кэширования классов в рамках запроса в PHP существует ещё две ступени:
+
В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они дёргают разные функции от $this, подразумевая, что это объект класса VMXTemplate.
* Текст шаблонов кэшируется в 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> секунд. В остальном всё проще — компилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в my-переменной пакета VMX::Template и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода.
+
Поэтому компилированный шаблон PHP-версии — это класс, производный от класса VMXTemplate. Как уже сказано выше, единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий функции шаблона (&lt;!-- FUNCTION … --&gt;).
  
 
==== Несколько различается действие <tt>use_utf8 = true</tt> ====
 
==== Несколько различается действие <tt>use_utf8 = true</tt> ====
Строка 461: Строка 488:
 
* PHP: «использовать mb_str* функции для работы со строками в выражениях».
 
* PHP: «использовать mb_str* функции для работы со строками в выражениях».
 
* Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.
 
* Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.
*: Для приведения всех переменных шаблона к UTF-8 можно использовать функцию <tt>utf8on()</tt> из [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} VMX::Common] (рекурсивный <tt>Encode::_utf8_on()</tt>).
+
*: Для приведения всех переменных шаблона к UTF-8 можно использовать функцию <tt>utf8on()</tt> из <tt>VMXTemplate::Utils</tt> (рекурсивный <tt>Encode::_utf8_on()</tt>).
  
 
==== Различается способ вывода ошибок при <tt>print_error = true</tt> ====
 
==== Различается способ вывода ошибок при <tt>print_error = true</tt> ====
Строка 471: Строка 498:
 
==== Различается поведение сравнений ====
 
==== Различается поведение сравнений ====
  
* PHP: Обычные сравнения — типозависимые.
+
* PHP: Тип обычных операторов сравнения определяется во время выполнения. То есть, если во время выполнения одно из сравниваемых значений — число, они сравниваются как числа, иначе — как строки.
* Perl: EQ и т. п. без S/N эквивалентно строковому (Sxx).
+
* Perl: Тип обычных операторов сравнения определяется ''во время компиляции''. То есть, если из контекста понятно, что одно из сравниваемых значений — число (если это константа или результат, например, функции count), сравнение будет численным, иначе — строковым.
 +
* Пустые массивы и хеши ложны в PHP и истинны в Perl. То есть простая проверка «IF array» (приведение к булеву типу), если array пуст, в PHP вернёт false, а в Perl — true.
  
 
==== Различается поведение некоторых функций работы с массивами и хешами ====
 
==== Различается поведение некоторых функций работы с массивами и хешами ====
Строка 492: Строка 520:
 
Ответы:
 
Ответы:
 
* Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.
 
* Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.
* Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это сразу же ликвидирует:
+
* Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это приблизительно называется MVP (Model-View-Presenter; View имеет связь с моделью только через Presenter) и сразу же ликвидирует:
 
** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.
 
** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.
 
** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.
 
** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.
 +
* Второй вариант — классический MVC, шаблон — это View (представление), во View передаётся модель, и View отображает состояние модели так, как ему хочется. То есть, шаблон общается напрямую с живыми объектами модели, которые ему передают, «обратная связь» присутствует.
 +
*: {{Warning}} Важное ИМХО! Частая проблема классического подхода — по 100 запросов для чтения одного и того же свойства при отображении 100 объектов выборки, вместо того, чтобы прочитать это свойство за 1 запрос сразу для всех 100 объектов. Чтобы такого не было — нужно, чтобы объекты помнили, частью какой коллекции они являются, и при чтении свойства читали его сразу для всех объектов «своей» коллекции. Идея основана на предположении, что если у объекта, прочитанного из БД как часть большой выборки, запрашивается какое-то свойство — велика вероятность того, что это же свойство будет запрошено и у всех остальных объектов той же самой выборки. При такой реализации — и писать удобно (не нужно заморачиваться, что передавать в шаблон, а что нет), и производительность не страдает.
  
 
Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:
 
Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:
Строка 504: Строка 534:
 
Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость.
 
Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость.
  
[[Категория:Sway]]
+
[[Категория:Техактивы]]
[[Категория:Разработка]]
+
 
[[Категория:Perl]]
 
[[Категория:Perl]]
 
[[Категория:PHP]]
 
[[Категория: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). Эти функции вызываются в контексте объекта шаблона с параметрами, равными коду для вычисления соответствующего аргумента, и должны возвращать код для вычисления результата. То есть, они выполняются на этапе компиляции.

Первое, что обычно нужно — это 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).

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