Шаблонизатор VMX::Template

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

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

Do you want to try some new features? By joining the beta, you will get access to experimental features, at the risk of encountering bugs and issues.

Ок Нет, спасибо

Содержание

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

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).

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