Изменения

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

12 821 байт убрано, 17:34, 20 апреля 2013
м
Нет описания правки
Данный модуль представляет собой новую версию шаблонизатора '''VMX::Template'''- высокопроизводительный шаблонизатор, построенную на некоторых новых идеях, ликвидировавшую безобразие имеющий Perl- и legacyPHP-версии (основная -код, накопленный в [[/Старая версия|старой версии]], однако сохранившую высокую производительность и простотуданный момент PHP).
Есть [* Лицензия: GNU GPL версии 3 или новее* Исходники (PHP): {{SVN|vitaphotovitalif/phptrunk/Template/}} - для работы нужны файлы template.phpи template.parser.php. Исходник грамматики в [{{SVN|vitalif/trunk/Template/template.lime}} PHP-версияtemplate.lime] и * Исходники (Perl), несколько устаревшие: [{{SVN|vitaphoto/solstice/lib-sway/VMX/Template.pm}} Perl-версия] шаблонизатораTemplate. Реализацияpm], естественно, несколько отличается по причине различий языков — например, в Perl’е [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} Common.pm]* Простой файл настроек для кэширования кода используются coderef’ы, а подсветки синтаксиса шаблонов в PHP предполагается, что кэшированием занимается какой-нибудь [http://xcachewww.lighttpdmidnight-commander.netorg/ XCacheMidnight Commander] или [http:[{{SVN|vitalif/trunk/eacceleratorscripts/tpl.net/ eAcceleratorsyntax|markup}} tpl.syntax], ибо там сохранить coderef между запросами, по-видимому, невозможно.
Развивается то одна, то другая, в зависимости от проекта, над которым я работаю в моменте.== Что это за шаблонизатор? ==
Также есть простенький (и кривоватенький) файл настроек синтаксиса шаблонов для [http://www.midnight-commander.org/ Midnight Commander]'а''VMX: :Template''' изначально реализован по мотивам примитивного шаблонизатора, взятого из кода форума phpBB 2 ([[{{SVN|vitalif/trunk/scripts/tpl.syntaxСтарая версия|markup}} tpl.syntaxисторическое описание старой версии].]), и с тех пор переписанного 4 раза:# На регулярные выражения →# На поиск подстроки →# На метод рекурсивного спуска →# И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME
Работаю над транслятором с {{CPAN|Template::Toolkit}} на VMX::Template. (ибо TT — задралЕсть PHP и Perl версии шаблонизатора, скотина!)основная версия шаблонизатора - в данный момент PHP. Код транслятора основан на оригинальной {{CPAN|Parse::Yapp}}Есть некоторые различия реализации -грамматике из комплекта поставки TT и находится по адресунапример, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь [http: //xcache.lighttpd.net/ XCache] или [{{SVN|vitaphotohttp:/solstice/tt2vmx}} tt2vmxeaccelerator.net/ eAccelerator], ибо там сохранить coderef между запросами невозможно.
<span style="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно Template можно сказать «ох уж эти перлисты — перлисты — что ни пишут, всё Template::Toolkit получается».</span> Это к тому, что идея вообще-то схожая, но реализация гораздо проще и быстрее. == Зачем нужен шаблонизатор == Ответы:* Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.* Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это сразу же ликвидирует:** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект. Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:* ''Руками'' писать в «шаблонном стиле»:*# Не смешивать сложные конструкции с HTML.*# Шаблоны выносить в отдельные функции и общаться с ними через 1 ассоциативный массив с данными.*# Вывод не печатать, а буферизовать (ob_start() &rarr; ob_get_contents() &rarr; ob_end_clean()) и возвращать.* Использовать читаемый стиль кода (?> и <?php только в конце строк, отступы насквозь через PHP и HTML).Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость. == Идеи для новой новой версии == Точно:* Операторы (по грамматике) — всё-таки эти OR(a,AND(b,c)) выглядят костыльно.* Доступ к элементам массивов в стиле JS — a.x или a[var]. В противовес TT, в котором a.$var, и текущему get(a, var).* Именованные параметры функций в шаблонах, более удобный синтаксис их вызова. Возможно:* Чуть более хитрую обработку пробелов ([%+ и [%- как в TT, режим для обрезания пробелов в начале строки).* Если в коде шаблона очень много инструкций — предупреждение «а не пора бы вам это перенести в код».* Наследование шаблонов.* HTML-режим, в котором по умолчанию экранируется всё, а чтобы не экранировать или экранировать иначе, нужно специально навесить сверху нужное преобразование, что-то типа <tt>{raw(value)}</tt> или <tt>{js(value)}</tt>. Хотя возможны проблемы с, скажем, JS внутри значения параметров типа onclick. Ни в коем случае:* Не добавлять фильтры :) это те же функции, только зачем-то в другом синтаксисе, некомбинируемом с другими вещами, и менее удобном.* Не делать идентичным синтаксис вызова метода объекта и получения элемента хеша (как в TT и ещё много где). Убрать:* legacy-синтаксис функций {var/s}.* Вероятно, убрать старый вариант блоков BEGIN b AT x BY y TO z (нах.. он не нужен). Пока непонятно, нужно ли и в каком виде (идеи):# Поддержка проверки формата входных данных?# «Классо-образный» синтаксис вызова функций из шаблонов?# Концепт номер раз# Концепт номер два === Концепт номер раз: Декларативные шаблоны === Концептуальная идея: «декларативные» шаблоны и «функциональная» их обработка, в противовес «процедурной». То есть, сначала разбиение шаблона на части (статические и вычисляемые), потом в негарантированном порядке замена вычисляемых элементов. За этим стоит идея — если мы будем так обрабатывать запросы в код, это даст возможность коду узнавать, какие же реально шаблону нужны данные, и вытаскивать из базы ровно их. То есть, как бы шаблоны, которые можно выполнять с любого места в любой момент. То есть либо убрать инструкцию SET, либо превратить её в по сути DEFINE :) И сразу концепция, которая бы дала возможность коду «узнавать»: «генераторы». Генератор — это некая функция, которая может дать много ответов сразу, причём быстрее, чем если каждый из них давать по одному. Но конкретный набор нужных данных неизвестен, генерировать и выводить в шаблон всё сразу не хочется, а хочется заюзать ленивость по полной. Шаблонизатор мог бы пройтись по всему коду шаблона, собрать встреченные вызовы генераторов, смело сгруппировать их и обработать пачками. Но до этого вряд ли дело дойдёт. === Концепт номер два: HTML-компоненты === Мысль: классический подход к шаблонизации не совсем удобен для всё чаще и чаще необходимых динамических интерфейсов, которые с одной стороны должны выводиться через HTML, а с другой — уметь меняться через JS. При обычном подходе логику зачастую приходится дублировать, а иногда и вставлять довольно некрасивые подпорки. Соответственно, идея: возможность компиляции частей шаблона («компонентов») как в HTML, так и в JS, генерирующий HTML. Круче всего, если бы оно умело само:* Создавать экземпляр JS-объекта, инкапсулирующего данный «компонент»* Автоматически цеплять этот объект к уже выведенному HTML-коду* Автоматически привязывать события к конкретным элементам внутри «компонента»* Обновлять DOM дерево не целиком (innerHTML), а по частям, конкретные элементы* Автоматически обновлять представление компонента при изменении полей JS-объекта*: Это возможно, так как шаблонизатор в курсе (большинства) зависимостей частей представления от конкретных элементов данных* Уметь «переключать» представление на новый объект, также меняя только его части == Идеи (от прошлой версии) == Уйти от assign_vars(), assign_block_vars(). Передавать, как и в обычных движках, просто хеш с данными $vars. Как, например, в {{CPAN|Template::Toolkit}}. При этом сохранить данные методы для совместимости. Почистить синтаксис: ликвидировать «преобразования», «вложенный путь по переменной» (->key->index->key->и т. п.), специальный синтаксис для окончания SET, неочевидное обращение к счётчику block.#, tr_assign_* и т. п. Переписать с нуля компилятор. Добавить в употребление функции, но только самые необходимые. Добавить обработку ошибок и диагностические сообщения.
== Использование ==
</source>
=== Различия =Синтаксис шаблонов ==
'''Кэширование работает по-разному.''' В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая: * Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl</tt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит. Иными словами, ''никто больше не использует CGI''. Таким образом, мы смело можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами.* PHP: интерпретатор PHP инициализируется заново при обработке каждого HTTP-запроса. А живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно. В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они используют контекст класса Template. Поэтому компилированный шаблон PHP-версии — это класс, производный от класса Template. Единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий блоку шаблона (см. [[#Блоки]]). Кроме кэширования классов в рамках запроса в PHP существует ещё две ступени:* Текст шаблонов кэшируется в XCache или eAccelerator, если таковые присутствуют, и не перезагружается с диска лишний раз. Если <tt>reload = false</tt>, лишними считаются все разы, кроме первого, даже если файл шаблона менялся.* Компилированный код шаблонов кэшируется в файлах на диске, и не компилируется лишний раз. В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как <tt>reload = false</tt> в PHP, но если <tt>reload > 0</tt>, то тексты шаблонов перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд. В остальном всё проще — компилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в my-переменной пакета VMX::Template и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода. '''Несколько различается действие <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> из [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} VMX::Common] (рекурсивный <tt>Encode::_utf8_on()</tt>). '''Различается способ вывода ошибок при <tt>print_error = true</tt>.''' * Общий смысл — при <tt>print_error = true</tt> ошибки и предупреждения должны попасть на экран.* PHP: они просто выводятся print()'ами.* Perl: здесь так нельзя, потому что HTTP-заголовки сами могут и не отправиться, поэтому Шаблон — любой текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом <tt>parse()</tt>). '''Различаются аргументы, передаваемые в <tt>compiletime_functions</tt>.''' * PHP: просто список кода выражений всех аргументов вызова. Функциятипично -компилятор вызывается вне контекста объекта.* Perl: тот же список + <tt>$self</tt> (объект VMX::TemplateHTML) в качестве первого элемента. '''Различается поведение функций сравнения.''' * PHP: EQ и т. п. без S/N — типозависимое сравнение.* Perl: EQ и т. п. без S/N эквивалентно строковому (Sxx). '''Различается поведение некоторых функций работы с массивами и хешами.''' * 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-версия не зависит ни от чего (кроме PHP 5), а Perl-версия зависит от [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} VMX::Common].''' '''В PHP-версии в шаблоны не включаются C-подобные «прагмы» #line, а в текст ошибок не включается имя файла шаблона и строка. Ибо решил — раз уж #line не поддерживается, нечего на строки заморачиваться.''' == Реализация == Шаблон — любой текст, в который ''местами'' включены директивы и/или подстановки. Маркеры начала и конца директивы <tt><nowiki><!-- --></nowiki></tt> и подстановки <tt><nowiki>{ }</nowiki></tt> могут быть заменены любыми другими. Если, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>. Маркеры подстановки можно вообще не задавать — тогда для них используются маркеры директив. * Подстановка — любое выражение, состоящее из переменных <tt>{a.b.0.c}</tt> и вызовов функций <tt>{eq(a.b.0.c, 'hello world')}</tt>.* Директивы — это FOR, IF, END и прочие, либо тоже выражение. Разница между выражением в директиве и выражением в подстановке проявляется, если маркеры подстановки заданы — в этом случае подстановки вычисляют выражение и подставляют результат в выходной поток, а директивы вычисляют выражение и никуда его не подставляют — игнорируют. Исключение из этого правила: функции подстановки (см. [[#Включения|Включения]]) подставляется в выходной поток всегда, и чтобы игнорировать результат включения шаблона, нужно явно указать void <tt><nowiki><!-- void include(...) --></nowiki></tt>. Путь к переменной теперь может включать в себя числа. Это будут обращения к элементам массивов, в то время как всё остальное — обращения к элементам хешей. Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) — '''регистронезависимы'''. Имена методов объектов, переданных в переменных, '''регистрозависимы'''. Итак, <tt><nowiki><!-- FOR x = y --></nowiki></tt> — директива кода, <tt>{a.b.0.c}</tt> — подстановка выражения. Комментарии: <tt><nowiki><!--# комментарий --></nowiki></tt> === Циклы === Вне блока {block} будет иметь значение ARRAY(0x…), то есть массив всех итераций блока block, а {block.0} будет иметь значение HASH(0x…), то есть первую итерацию блока block. <pre><!-- BEGIN block --></pre> Теперь, внутри блока {block} теперь будет иметь значение HASH(0x…), то есть уже значение текущей итерации блока block, а {block.#} будет иметь значением номер текущей итерации блока, отсчитываемый с 0, а не с 1, как в старой версии. <pre><!-- END block --></pre> На <tt><nowiki><!-- END другоеимя --></nowiki></tt> после <tt><nowiki><!-- BEGIN block --></nowiki></tt> при <tt>strict_end = true</tt> шаблонизатор выдаст ошибку, «ибо нефиг» (c). Если block в хеше данных — не массив, а хеш — это значит, что итерация у блока только одна, и тогда <tt><nowiki><!-- BEGIN block --></nowiki></tt> работает <s>как for($expression) {} в Perl</s> никак. <tt>BEGIN ... END</tt> — это циклы в «старом стиле». А можно использовать и TT-подобный, обычно более удобныйПример:
<pre>
<!-- FOR var = expression SET title --> Статистика<!-- END --><!-- SET headscripts --> <script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jquery.min..js"></script>
<!-- END -->
<!-- INCLUDE "admin_header.tpl" -->
<!-- IF NOT srcid -->
<p>Добро пожаловать в простую OLAPообразную статистику. Выберите источник данных:</p>
<form action="?" method="GET">
<select style="width:100px" name="datasource">
<!-- FOR s = sources -->
<option value="{s s.id}">{s s.name}</option>
<!-- END -->
</select>
<input type="submit" value=" Продолжить " />
</form>
<!-- ELSEIF srcid == "test" || sources[srcid].mode == 'test' -->
<p>Тестовый режим.</p>
<!-- END -->
<!-- INCLUDE "admin_footer.tpl" -->
</pre>
Причём, если Маркеры:* <tt>expression ::= block</ttnowiki>, то '<tt!--', '-->var'</ttnowiki> может быть само <tt>block</tt>'ом. Это, по сути, - маркеры начала и есть то, что делает BEGIN: конца директивы* <tt><nowiki><!-- BEGIN block -->'{', '}'</nowiki></tt> эквивалентно - маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т.п.)* Подстановки можно использовать и в директивах* Маркеры начала и конца директивы можно заменить другими - tсли, например, вы привыкли к TT, можно установить <tt><nowiki><!-- FOR block = block -->[% %]</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 &#x7C;&#x7C; b, a OR b | Логическое ИЛИ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>
<pre><!-- IF OR(function(block.key1),AND(block.key2,block.key3)) --></pre>Директивы:
Почему? Тут всё просто — основываясь на предположении, что длинные <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>
Синтаксис вызова функции нескольких аргументов: <pre><!-- function(block.key, 0, "abc") --></pre> Подстановка: <pre>{function(block.key, 0, "abc")}</pre> Синтаксис вызова функции одного аргумента: <pre><!-- function(block.key) --><!-- function block.key -->{block.key/s}{s block.key}</pre> Синтаксис вызова метода объекта: <pre>{object.method()}{object.method(arg1, arg2)}{call(object, "method")}{call(object, "method", arg1, arg2)}</pre> Последние два применения — как нетрудно заметить, обращение к функции call() и служат для вызова метода по вычисляемому имени. Цепочки вызовов методов типа <tt>object.method().another_method()</tt> не поддерживаются, ибо к ним без сохранения звеньев нервно относится даже сам PHP. === IF Функции === Условный вывод: <pre><!-- IF function(block.key) --><!-- ELSEIF ... --><!-- END --><!-- IF NOT block.key -->...<!-- END --> </pre> ELSIF эквивалентно ELSE IF и ELSEIF. === SET === Запись значения переменной: <pre><!-- SET block.key -->...<!-- END --></nowiki><!-- SET block.key = выражение --></pre> === Включения === Включение другого шаблона также осталось: <pre><!-- INCLUDE another-file.tpl --><!-- INCLUDE "another-file.tpl" --></pre> По «динамическому» имени шаблона включение производится [[#Включения|функциями включения]]. Как несложно заметить, вторая строка — как раз вызов функции. === Блоки === Блок — это часть шаблона, выделенная в отдельную «функцию», хорошо кэшируемая и предназначенная для повторного вызова из других мест. Покрывает сразу несколько вещей — «блоки», «макросы» и «обёртки» из TT. Да-да, TT славится бессмысленным дублированием функционала. Наши блоки имеют несколько преимуществ:* блоки, определённые в одном шаблоне, можно смело вызывать из других по имени файла + имени блока!* блок можно определить просто как некоторое выражение.* блоки хорошо кэшируются — с VMX::Template вы не испытаете разочарования, если вызовете какой-нибудь блок 1000 раз. В отличие от TT. Блоки в шаблоне не могут быть вложенными, а циклы, SET и прочие вещи, их оборачивающие, не имеют на них никакого влияния. После компиляции блоки просто вырезаются и преобразуются в отдельные функции PHP/Perl’а. <pre><!-- BLOCK имя_блока -->...код...<!-- END --></pre> или <pre><!-- BLOCK имя_блока = выражение --></pre> Вместо слова <tt>BLOCK</tt> можно также использовать слово <tt>FUNCTION</tt> или <tt>MACRO</tt>. Вызывать блок из шаблона следует с помощью функций [[#EXEC|EXEC]], [[#EXEC_FROM|EXEC_FROM]]. Вызывать блок из кода следует с помощью функций [[#EXEC_FROM_INLINE]]. Также см. [[#Использование]]. == Функции ==
Функции используются в выражениях как <tt>ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...)</tt> или как <tt>ФУНКЦИЯ (пробел) ОДИН_АРГУМЕНТ</tt>. Вместо запятой ", " можно также использовать «=>», например <tt>HASH(КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ)</tt>. Синтаксической разницы между ", " и «=>» никакой нет.
Расширяемость в области функций:
;Run-time функции: В качестве функции можно использовать метод переданного в хеше данных объекта. В «функцию» можно вынести и блок кода из шаблона — шаблона — см. [[#Блоки]]. Оно хорошо кэшируется.;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>, равный хешу, в котором ключи — ключи — имена дополнительных функций, а значения — значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', и должны возвращать '''код для вычисления результата'''. То есть, они выполняются на этапе компиляции.
{{note}} Первое, что обычно нужно — нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:
* S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.* H() — удаляет все HTML-теги, кроме «безопасных».* T() — удаляет все HTML-теги.* Q() — это addslashes(), экранирует строки для использования, например, в JS.* I() — преобразует значение к целому числу.
=== Числа, логические операции ===
 
==== OR, AND, NOT ====
 
Логические ИЛИ, И, НЕ, действующие аналогично Perl операторам ||, &&, !.
 
==== ADD, SUB, MUL, DIV, MOD ====
 
Арифметические операции + — * / %.
==== LOG ====
Преобразование к целому числу.
 
==== EQ, NE, GT, LT, GE, LE ====
 
Сравнения == != > < >= <= аргументов как строк (Perl) или типо-зависимое сравнение (PHP). В PHP если хотя бы один из аргументов численный, сравниваются они как числа.
==== SEQ, SNE, SGT, SLT, SGE, SLE ====
Аргументы сравниваются всегда как строкиСтроковые сравнения.
==== NEQ, NNE, NGT, NLT, NGE, NLE ====
Аргументы сравниваются всегда как числаЧисленные сравнения.
==== YESNO ====
Тернарный YESNO($1, $2, $3) - тернарный оператор ($1 ? $2 : $3).
=== Строки ===
==== 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]).
==== URI_QUOTE=URIQUOTE=URLENCODE ====
==== REPLACE, STR_REPLACE ====
* Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — строке — replace(RegExp, замена, строка). * Замена подстроки в строке — строке — str_replace(искомое, замена, строка).
==== STRLEN ====
==== SUBSTR=SUBSTRING ====
Стандартная (для всех, кроме жавистов) функция подстроки — подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.
==== TRIM ====
==== SPLIT ====
Разделение строки по регулярному выражению и лимиту — лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split])
==== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ====
* Преобразование символов < > & " ' в HTML-сущности. * Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов. * Удаление только «небезопасных» HTML-тегов. * Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>.
==== CONCAT, JOIN=IMPLODE ====
* Конкатенация всех своих аргументов — аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов. * Конкатенация элементов массива через разделитель — разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.
==== SUBST, SPRINTF, STRFTIME ====
Подстановка Subst - подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — массива — subst(строка, $1, $2, …).
Sprintf — Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf].
Форматирование даты и/или времени с помощью функции [http://www.manpagez.com/man/3/strftime/ strftime] — strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:
* UNIX время.
* Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — конце — опционально временная зона — зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря, <pre>^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$</pre>
* Оракловский формат даты-времени: <nowiki>ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС</nowiki>.
* RFC 822.
==== STRLIMIT=TRUNCATE ====
Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> — <tt>strlimit(str, len, dots = "...")</tt>. Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется добавляется <tt>dots</tt> или по умолчанию <tt>"..."</tt>, если аргумент <tt>dots</tt> не передаётся.
=== Массивы и хеши ===
Создание хэша из всех аргументов.
Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и ти т. пп.
==== KEYS, HASH_KEYS, ARRAY_KEYS ====
Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — Perl — нет.
==== SORT ====
==== ARRAY, RANGE ====
* Создание массива. * Диапазон от A до B — B — range(A, B).
==== IS_ARRAY ====
==== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ====
* Количество элементов массива, или 0, если аргумент — аргумент — не массив — массив — count(аргумент).* Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.* Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).
Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.==== GET ====
Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod). ==== GET, AGET, HGET ==== Получение элемента массива/хэша по «динамическому» ключу. По-моему, это лучше, чем зюки-хрюки Template Toolkit’а: <tt>hash.${hash2.$key}</tt> и т. п. <tt>GET(откуда, что)</tt> автоматически решает, «откуда» — это массив или хеш, AGET служит только для массивов, а HGET только для хешей. В PHP-версии все три идентичны. <tt>GET(что)</tt> — получение значения переменной верхнего уровня. Стоит отметить, что PHP-версия для выражений, подобных <tt>get(function_that_returns_array(), 'array_key')</tt> вместо запрещённого синтаксисом PHP выражения <tt>(function_that_returns_array()['array_key'])</tt> автоматически использует выражение вида <tt>self::exec_get(function_that_returns_array(), 'array_key')</tt>.
==== SET ====
<tt>SET(куда, что)</tt> — присваивание «куда» значения «что». Уравнения, понятное дело, не решает, то есть, как и обычно, присваивать можно только lvalue :)
==== ARRAY_MERGE ====
Слить массивы в один. Под Perl — Perl — только массивы (не хеши), под PHP — PHP — любые массивы.
==== 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>.
 
Также имеют особенность: результат этих функций подставляется даже при использовании в директивах. То есть, например, если маркеры директив <tt><nowiki><!-- --></nowiki></tt>, а подстановок <tt><nowiki>{ }</nowiki></tt>, то <tt><nowiki><!-- include(...) --></nowiki></tt> всё равно будет подставлено. Игнорировать результат нужно явно: <tt><nowiki><!-- void include(...) --></nowiki></tt>.
==== PARSE=INCLUDE=PROCESS ====
==== PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE ====
Включение кода не из файла, а просто из строки — строки — <tt>parse_inline('код шаблона'[, аргументы])</tt>.
==== EXEC ====
Включение блока из текущего шаблона — шаблона — <tt>exec('имя блока'[, аргументы])</tt>.
==== EXEC_FROM ====
Включение блока из другого шаблона — шаблона — <tt>exec_from('имя файла', 'имя блока'[, аргументы])</tt>.
==== EXEC_FROM_INLINE ====
Сие Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — строки — <tt>exec_from_inline('код шаблона', 'имя блока'[, аргументы])</tt>.
=== Прочее ===
==== DUMP=VAR_DUMP ====
Вывод всех данных из структуры — структуры — Dumper в Perl’е и var_dump в PHP.
==== JSON ====
==== CALL ====
Вызов метода объекта по «динамическому» имени — имени — <tt>call(varref, method_name, arg1, arg2, arg3, ...)</tt>.
==== 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 (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами.* PHP: интерпретатор PHP инициализируется заново при обработке каждого HTTP-запроса. А живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно. В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они используют контекст класса Template. Поэтому компилированный шаблон PHP-версии — это класс, производный от класса Template. Единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий блоку шаблона (см. [[#Блоки]]). Кроме кэширования классов в рамках запроса в PHP существует ещё две ступени:* Текст шаблонов кэшируется в XCache или eAccelerator, если таковые присутствуют, и не перезагружается с диска лишний раз. Если <tt>reload = false</tt>, лишними считаются все разы, кроме первого, даже если файл шаблона менялся.* Компилированный код шаблонов кэшируется в файлах на диске, и не компилируется лишний раз. В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как <tt>reload = false</tt> в PHP, но если <tt>reload > 0</tt>, то тексты шаблонов перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд. В остальном всё проще — компилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в my-переменной пакета VMX::Template и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода. ==== Несколько различается действие <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> из [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} VMX::Common] (рекурсивный <tt>Encode::_utf8_on()</tt>). ==== Различается способ вывода ошибок при <tt>print_error = true</tt> ==== * Общий смысл — при <tt>print_error = true</tt> ошибки и предупреждения должны попасть на экран.* PHP: они просто выводятся print()'ами.* Perl: здесь так нельзя, потому что HTTP-заголовки сами могут и не отправиться, поэтому текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом <tt>parse()</tt>). ==== Различаются аргументы, передаваемые в <tt>compiletime_functions</tt> ==== * PHP: просто список кода выражений всех аргументов вызова. Функция-компилятор вызывается вне контекста объекта.* Perl: тот же список + <tt>$self</tt> (объект VMX::Template) в качестве первого элемента. ==== Различается поведение сравнений ==== * PHP: Обычные сравнения - типозависимые.* Perl: EQ и т. п. без S/N эквивалентно строковому (Sxx). ==== Различается поведение некоторых функций работы с массивами и хешами ==== * 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. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это сразу же ликвидирует:** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект. Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:* ''Руками'' писать в «шаблонном стиле»:*# Не смешивать сложные конструкции с HTML.*# Шаблоны выносить в отдельные функции и общаться с ними через 1 ассоциативный массив с данными.*# Вывод не печатать, а буферизовать (ob_start() &rarr; ob_get_contents() &rarr; ob_end_clean()) и возвращать.* Использовать читаемый стиль кода (?> и <?php только в конце строк, отступы насквозь через PHP и HTML).Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость.
[[Категория:Sway]]