Шаблонизатор VMX::Template — различия между версиями
м (→Идея для новой новой версии) |
|||
(не показано 65 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
− | + | '''VMX::Template''' — простой и высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии. | |
− | + | * Лицензия: 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. | ||
− | + | == Что это за шаблонизатор? == | |
− | + | '''VMX::Template''' изначально реализован по мотивам примитивного шаблонизатора, взятого из кода форума phpBB 2 ([[/Старая версия|историческое описание старой версии]]), и с тех пор (с 2006 года) переписанного 4 раза: | |
+ | # На регулярные выражения → | ||
+ | # На поиск подстроки → | ||
+ | # На метод рекурсивного спуска → | ||
+ | # И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME | ||
− | + | Есть 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:: | + | <span style="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается».</span> |
− | + | Это к тому, что и идея, и синтаксис шаблонов вообще-то схожи, но сама реализация гораздо проще и быстрее. | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
== Использование == | == Использование == | ||
− | + | === 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' => | + | '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 для строковых операций | ||
Строка 84: | Строка 47: | ||
'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), |
# которому передаются скомпилированные выражения всех аргументов | # которому передаются скомпилированные выражения всех аргументов | ||
− | # | + | 'strip_space' => true, # встроенный фильтр: срезание пробелов из начал и концов строк |
− | ' | + | 'filters' => # фильтры - выполняются над выводом всех шаблонов |
− | + | array(callback1, ...), | |
− | ' | + | 'auto_escape' => false, # функция авто-экранирования, например "s" (для HTML-безопасного режима) |
)); | )); | ||
Строка 116: | Строка 79: | ||
# Очистка сохранённых данных для генерации ещё одной страницы: | # Очистка сохранённых данных для генерации ещё одной страницы: | ||
$template->clear; | $template->clear; | ||
− | </ | + | </code-php> |
=== Perl === | === Perl === | ||
+ | |||
+ | Perl версия обновлена и теперь в точности соответствует PHP-версии. | ||
<source lang="perl"> | <source lang="perl"> | ||
− | use | + | use VMXTemplate; |
# Конструктор | # Конструктор | ||
− | $template = new | + | $template = new VMXTemplate( |
'root' => '.', # директория с шаблонами | 'root' => '.', # директория с шаблонами | ||
'cache_dir' => undef, # директория для кэширования компилированного кода шаблонов | 'cache_dir' => undef, # директория для кэширования компилированного кода шаблонов | ||
Строка 130: | Строка 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' => '-->', # маркер конца директивы кода | ||
− | |||
'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, | ||
− | # которому передаются скомпилированные выражения всех аргументов и первым - | + | # которому передаются скомпилированные выражения всех аргументов и первым - парсер (объект VMXTemplate::Parser) |
− | # | + | 'filters' => [ sub {}, .. ], # фильтры для запуска на выводе каждого внешнего шаблона (фильтр - функция, модифицирующая $_[0]) |
− | ' | + | 'strip_space' => 0, # если TRUE, удалять пробелы и табы из начала и конца всех строк вывода |
− | ' | + | 'auto_escape' => '', # функция авто-экранирования, например "s" (для HTML-безопасного режима) |
); | ); | ||
− | + | $template->vars({ | |
− | $template->vars( | + | var => 'value', |
+ | ... | ||
+ | }); | ||
# Выполнения полностью аналогичны PHP: | # Выполнения полностью аналогичны PHP: | ||
Строка 158: | Строка 127: | ||
</source> | </source> | ||
− | == | + | == Синтаксис шаблонов == |
− | ''' | + | Шаблон — любой текст (типично — HTML), в который ''местами'' включены директивы и/или подстановки. |
− | + | Если вы знаете о порождающих грамматиках, то вот контекстно-свободная грамматика для 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 | + | <!-- 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><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>||</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><!--# Комментарий --></tt> | Комментарий | ||
+ | <tt><!-- FOR item = array --></tt><br />…код…<br /><tt><!-- END --></tt> | Цикл. Вместо FOR можно использовать слово FOREACH. Внутри цикла можно обратиться к счётчику через <tt>{item_index}</tt> | ||
+ | <tt><!-- IF выражение --></tt><br />…код… | Если (выражение) | ||
+ | <tt><!-- ELSEIF выражение --></tt><br />…код… | Иначе если (выражение) | ||
+ | <tt><!-- ELSE --></tt><br />…код… | Иначе | ||
+ | <tt><!-- END --></tt> | Конец если / цикла / присваивания | ||
+ | <tt><!-- SET var = выражение --></tt> | Присваивание переменной var результата выполнения выражения | ||
+ | <tt><!-- SET var --></tt><br />…код…<br /><tt><!-- END --></tt> | Присваивание переменной var результата выполнения кода | ||
+ | <tt><!-- FUNCTION name (arg1, arg2) = выражение --></tt> | Определение функции шаблона как результата выполнения выражения | ||
+ | <tt><!-- FUNCTION name (arg1, arg2) --></tt><br />…код…<br /><tt><!-- END --></tt> | Определение функции / «блока» шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO | ||
+ | </tab> | ||
− | + | === Функции === | |
− | Синтаксис вызова | + | Синтаксис вызова функций: |
− | + | * <tt>ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...)</tt> | |
− | + | * <tt>ФУНКЦИЯ <пробел> ОДИН_АРГУМЕНТ</tt> | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | * | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций. | Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций. | ||
Строка 364: | Строка 236: | ||
* 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-кода, поэтому лучше их вывод не трогать. | |
− | + | === Числа, логические операции === | |
==== LOG ==== | ==== LOG ==== | ||
Строка 385: | Строка 259: | ||
Преобразование к целому числу. | Преобразование к целому числу. | ||
− | |||
− | |||
− | |||
− | |||
==== SEQ, SNE, SGT, SLT, SGE, SLE ==== | ==== SEQ, SNE, SGT, SLT, SGE, SLE ==== | ||
− | + | Строковые сравнения. | |
==== NEQ, NNE, NGT, NLT, NGE, NLE ==== | ==== NEQ, NNE, NGT, NLT, NGE, NLE ==== | ||
− | + | Численные сравнения. | |
==== YESNO ==== | ==== YESNO ==== | ||
− | + | YESNO($1, $2, $3) — тернарный оператор ($1 ? $2 : $3). | |
=== Строки === | === Строки === | ||
Строка 414: | Строка 284: | ||
==== Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE ==== | ==== Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE ==== | ||
− | Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка). | + | * Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка). |
− | + | * Экранирование символа " удвоением — sql_quote(строка). (актуально также для [[rupedia:CSV|CSV]]) | |
− | Экранирование символа " удвоением — sql_quote(строка). (актуально также для [[rupedia:CSV|CSV]]) | + | * Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]). |
− | + | ||
− | Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]). | + | |
==== URI_QUOTE=URIQUOTE=URLENCODE ==== | ==== URI_QUOTE=URIQUOTE=URLENCODE ==== | ||
Строка 426: | Строка 294: | ||
==== REPLACE, STR_REPLACE ==== | ==== REPLACE, STR_REPLACE ==== | ||
− | Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка). | + | * Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка). |
− | + | * Замена подстроки в строке — str_replace(искомое, замена, строка). | |
− | Замена подстроки в строке — str_replace(искомое, замена, строка). | + | |
==== STRLEN ==== | ==== STRLEN ==== | ||
Строка 448: | Строка 315: | ||
==== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ==== | ==== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ==== | ||
− | Преобразование символов < > & " ' в HTML-сущности. | + | * Преобразование символов < > & " ' в HTML-сущности. |
− | + | * Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов. | |
− | Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов. | + | * Удаление только «небезопасных» HTML-тегов. |
− | + | * Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>. | |
− | Удаление только «небезопасных» HTML-тегов. | + | |
− | + | ||
− | Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>. | + | |
==== CONCAT, JOIN=IMPLODE ==== | ==== CONCAT, JOIN=IMPLODE ==== | ||
− | Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов. | + | * Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов. |
− | + | * Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов. | |
− | Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов. | + | |
==== SUBST, SPRINTF, STRFTIME ==== | ==== SUBST, SPRINTF, STRFTIME ==== | ||
− | + | Subst — подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …). | |
Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf]. | Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf]. | ||
Строка 476: | Строка 339: | ||
==== STRLIMIT=TRUNCATE ==== | ==== STRLIMIT=TRUNCATE ==== | ||
− | Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> — <tt>strlimit(str, len, dots = "...")</tt>. Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец | + | Ограничение длины строки <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> | ||
=== Массивы и хеши === | === Массивы и хеши === | ||
Строка 500: | Строка 369: | ||
==== ARRAY, RANGE ==== | ==== ARRAY, RANGE ==== | ||
− | Создание массива. | + | * Создание массива. |
− | + | * Диапазон от A до B — range(A, B). | |
− | Диапазон от A до B — range(A, B). | + | |
==== IS_ARRAY ==== | ==== IS_ARRAY ==== | ||
Строка 510: | Строка 378: | ||
==== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ==== | ==== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ==== | ||
− | Количество элементов массива, или 0, если аргумент — не массив — count(аргумент). | + | * Количество элементов массива, или 0, если аргумент — не массив — count(аргумент). |
+ | * Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP. | ||
+ | * Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod). | ||
− | + | ==== GET ==== | |
− | + | ||
− | + | ||
− | + | ||
− | ==== GET | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
<tt>GET(что)</tt> — получение значения переменной верхнего уровня. | <tt>GET(что)</tt> — получение значения переменной верхнего уровня. | ||
− | |||
− | |||
==== SET ==== | ==== SET ==== | ||
Строка 536: | Строка 396: | ||
==== SHIFT, POP, UNSHIFT, PUSH ==== | ==== SHIFT, POP, UNSHIFT, PUSH ==== | ||
− | Вынуть элемент из начала | + | * Вынуть элемент из начала массива — <tt>shift(array)</tt> |
+ | * Вынуть из конца — <tt>pop(array)</tt> | ||
+ | * Добавить в начало — <tt>unshift(array, value)</tt> | ||
+ | * Добавить в конец — <tt>push(array, value)</tt>. | ||
=== Включения === | === Включения === | ||
Сюда относятся функции выполнения других шаблонов и/или их блоков. Во все эти функции можно передавать «данные» (tpldata) либо с помощью создания хеша функцией hash(), либо просто передачей аргументов как <tt>КЛЮЧ => ЗНАЧЕНИЕ, ...</tt>. | Сюда относятся функции выполнения других шаблонов и/или их блоков. Во все эти функции можно передавать «данные» (tpldata) либо с помощью создания хеша функцией hash(), либо просто передачей аргументов как <tt>КЛЮЧ => ЗНАЧЕНИЕ, ...</tt>. | ||
− | |||
− | |||
==== PARSE=INCLUDE=PROCESS ==== | ==== PARSE=INCLUDE=PROCESS ==== | ||
Строка 568: | Строка 429: | ||
==== EXEC_FROM_INLINE ==== | ==== EXEC_FROM_INLINE ==== | ||
− | + | Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — <tt>exec_from_inline('код шаблона', 'имя блока'[, аргументы])</tt>. | |
=== Прочее === | === Прочее === | ||
Строка 575: | Строка 436: | ||
Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток. | Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток. | ||
+ | |||
+ | ==== RAW ==== | ||
+ | |||
+ | Пустое преобразование первого аргумента со снятием флага «небезопасности». Нужно для подстановки значений «как есть» в HTML-безопасном режиме авто-экранирования. | ||
==== DUMP=VAR_DUMP ==== | ==== DUMP=VAR_DUMP ==== | ||
Строка 592: | Строка 457: | ||
Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы). | Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы). | ||
− | [[ | + | == Изменения относительно старых версий == |
− | [[Категория: | + | |
+ | * Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() — теперь, как и обычно, передаётся просто хеш с данными $vars | ||
+ | * Синтаксис типа {a->key} ликвидирован | ||
+ | * Авто-переводы, которые были в перловой версии — тоже тю-тю (хотя, может, и будут возрождены) | ||
+ | * SET теперь заканчивается обычным END, а не ENDSET | ||
+ | * Обращение к счётчику цикла теперь {block_index}, а не {block.#} | ||
+ | * Добавлены функции, операторы | ||
+ | * Добавлены детальные сообщения об ошибках | ||
+ | * Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона | ||
+ | |||
+ | == Различия PHP и Perl версий == | ||
+ | |||
+ | ==== Кэширование работает по-разному ==== | ||
+ | |||
+ | В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая: | ||
+ | |||
+ | * 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, и вызывается метод класса, соответствующий функции шаблона (<!-- FUNCTION … -->). | ||
+ | |||
+ | ==== Несколько различается действие <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() → ob_get_contents() → ob_end_clean()) и возвращать. | ||
+ | * Использовать читаемый стиль кода (?> и <?php только в конце строк, отступы насквозь через PHP и HTML). | ||
+ | Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость. | ||
+ | |||
+ | [[Категория:Техактивы]] | ||
[[Категория:Perl]] | [[Категория:Perl]] | ||
[[Категория:PHP]] | [[Категория:PHP]] |
Текущая версия на 23:43, 27 июля 2018
VMX::Template — простой и высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии.
- Лицензия: GNU GPL версии 3 или новее
- Полный набор исходников здесь: http://yourcmc.ru/git/vitalif/VMXTemplate
- PHP-версия (PHP >= 5.4): template.php и template.parser.php.
- Новая Perl-версия: VMXTemplate.pm и все его подмодули VMXTemplate/*.pm.
- Старая Perl-версия: VMX/Template.pm и VMX/Common.pm.
- Исходный код грамматики PHP-версии (LALR(1) LIME): template.lime.
- Исходный код грамматики Perl-версии (LALR(1) Parse::Yapp): template.yp и template.skel.pm.
- Исходный код голой грамматики (LALR(1) yacc): template.y.
- В Git-репозитории можно найти полную историю разработки, начиная с самых старых phpbb-подобный версий.
- Простые настройки для подсветки синтаксиса шаблонов в Midnight Commander'а: tpl.syntax. Чтобы подсветка нормально выглядела, к tpl.syntax в начало надо дописать html.syntax из стандартного комплекта поставки mc.
Содержание
- 1 Что это за шаблонизатор?
- 2 Использование
- 3 Синтаксис шаблонов
- 3.1 Пример
- 3.2 Маркеры
- 3.3 Выражения
- 3.4 Операторы
- 3.5 Директивы
- 3.6 Функции
- 3.7 Числа, логические операции
- 3.8 Строки
- 3.8.1 LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE
- 3.8.2 LCFIRST, UCFIRST
- 3.8.3 Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE
- 3.8.4 URI_QUOTE=URIQUOTE=URLENCODE
- 3.8.5 REPLACE, STR_REPLACE
- 3.8.6 STRLEN
- 3.8.7 SUBSTR=SUBSTRING
- 3.8.8 TRIM
- 3.8.9 SPLIT
- 3.8.10 S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR
- 3.8.11 CONCAT, JOIN=IMPLODE
- 3.8.12 SUBST, SPRINTF, STRFTIME
- 3.8.13 STRLIMIT=TRUNCATE
- 3.8.14 PLURAL_RU
- 3.9 Массивы и хеши
- 3.10 Включения
- 3.11 Прочее
- 4 Изменения относительно старых версий
- 5 Различия PHP и Perl версий
- 6 А кстати, зачем вообще нужен шаблонизатор?
Что это за шаблонизатор?
VMX::Template изначально реализован по мотивам примитивного шаблонизатора, взятого из кода форума phpBB 2 (историческое описание старой версии), и с тех пор (с 2006 года) переписанного 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) алгоритма разбора:
- Голая Bison-грамматика, для получения представления о синтаксисе: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.y
- Рабочая LIME-грамматика: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.lime (для её работы нужен патченый LIME — см. https://github.com/vitalif/lime)
- Рабочая Parse::Yapp-грамматика: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.yp
Пример
<!-- 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: Важное ИМХО! Частая проблема классического подхода — по 100 запросов для чтения одного и того же свойства при отображении 100 объектов выборки, вместо того, чтобы прочитать это свойство за 1 запрос сразу для всех 100 объектов. Чтобы такого не было — нужно, чтобы объекты помнили, частью какой коллекции они являются, и при чтении свойства читали его сразу для всех объектов «своей» коллекции. Идея основана на предположении, что если у объекта, прочитанного из БД как часть большой выборки, запрашивается какое-то свойство — велика вероятность того, что это же свойство будет запрошено и у всех остальных объектов той же самой выборки. При такой реализации — и писать удобно (не нужно заморачиваться, что передавать в шаблон, а что нет), и производительность не страдает.
Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:
- Руками писать в «шаблонном стиле»:
- Не смешивать сложные конструкции с HTML.
- Шаблоны выносить в отдельные функции и общаться с ними через 1 ассоциативный массив с данными.
- Вывод не печатать, а буферизовать (ob_start() → ob_get_contents() → ob_end_clean()) и возвращать.
- Использовать читаемый стиль кода (?> и <?php только в конце строк, отступы насквозь через PHP и HTML).
Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость.