Изменения

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

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