Blitz Templates

$Id: blitz_ru.html,v 1.1 2006/11/20 06:26:08 fisher Exp $, akexey d0t rybak sorry-no-spam gmail d0t com

download the source and win32 binaries

Содержание
Зачем ещё один парсер шаблонов?
Некоторые результаты тестов производительности
Инсталляция
Вводный курс
Параметры настройки
Синтаксис Blitz и API

Зачем ещё один парсер шаблонов?

Blitz родился весьма неоригинально, for fun. Однако, поигравшись с ним немного, мне показалось, что скорость, с которой он работает, и удобства, которые он предоставляет разработчику - стоят того, чтобы дать его поиграться коллегам. Основных "фишек" у Blitz три:
- написан как PHP-модуль на Си, и является одним из самых быстрых движков
- имеет простой и интуитивно понятный синтаксис
- позволяет структурировать код удобным и легко читаемым образом

Blitz поддерживает разделение и скрытие функционально различных частей шаблонов с помощью простого механизма: текст шаблона может содержать вызов пользовательского метода объекта, который этим шаблоном управляет. Таким образом достигается основная цель: шаблон не содержит большого количества блоков и контекстов, часто мешающих разобраться, что к чему. Напротив, даже в проекте со сложной логикой представления при правильном подходе шаблоны будут давать разработчику своеобразную "карту" всего проекта. Blitz также позволяет включать одни шаблоны в другие (аналог include) и поддерживает условный вывод переменных (аналог if).

Начиная с версии 0.4 в blitz добавлен функционал движка php_templates. Теперь в blitz можно создавать множество вложенных шаблонов внутри одного файла, это сильно упрощает жизнь для очень сложных шаблонов с одной стороны и улучшает показатели по производительности для ряда задач с другой. Также с версии 0.4 есть экспериментальная поддержка работы с набором "упакованных" шаблонов (blitzpack), когда несколько шаблонов сначала единожды "компилируются" в бинарный формат, упаковываются в один файл, и затем работа ведется только с этой "бинарной пачкой" шаблонов, экономя время на файловых операциях и разборе шаблонов.

Мне бы не хотелось здесь следовать академической традиции и проводить подробный анализ других проектов. В любом случае, если вы научились эффективно использовать сам PHP в качестве шаблонного движка, обходясь без сторонних продуктов и библиотек - вы счастливый человек. Вы используете самый эффективный с точки зрения производительности подход, и если он вам удобен - придерживайтесь его. Если нет - попробуйте Blitz. Возможно, он вас приятно удивит ;)

Статус проекта - полуэкспериментальный, но я считаю текущую версию вполне работоспособной. В силу параноидального отношения к качеству проекта, 100%-я стабильность и обратная совместимость до версий 1.0 не гарантируется.

Некоторые результаты тестов производительности

С сожалению, мне неизвестна ни одна простая, универсальная и по-настоящему корректная методика анализа производительности шаблонных движков. А результатами любых искуственных, или как их ещё принято называть, синтетических, тестов пользоваться нужно максимально осторожно. Тем не менее, здесь приводятся результаты двух тестов. Первый тест - классический, измеряющий скорость выполнения циклических итераций одного и того же шаблона. Тест крайне простой, но позволяющий достаточно условно разделить группы движков на "нормальные", "медленные" и "никуда не годные". Число итераций и переменных в блоке было взято по умолчанию (9 переменных, 50 итераций), результаты этого теста приведены в таблице 1. Как легко видеть, Blitz по крайней мере аутсайдером не является.

тбл. 1
Тестовая машина(A): сервер XEON*2 2,4GHz (HT on) 2GB; linux php-4.3.10(fgci) zps nginx
blitz, php_templates: so-модули, CFLAGS: -g3 -O2

------------------------------------------------------
N         Engine name         Time         Percentage 
------------------------------------------------------
1           php              0.000544        100%     
2           blitz            0.001008        185%     
3           php_templates    0.001812        333%     
4           smarty           0.002006        369%     
5           str_replace      0.003713        683%     
6           phemplate        0.004514        830%     
7           fasttemplate     0.006835        1256%    
8           vtemplate        0.009565        1758%    
9           ultratemplate    0.012993        2388%    
10          templatepower    0.017056        3135%    
11          bugitemplate     0.019989        3674%    
12          phplib           0.028053        5157%    
13          profTemplate     0.043104        7924%    
14          xtemplate        0.048799        8970%    

Тестовая машина(B): PC PIV 2,8GHz (HT off) 1GB; linux-2.6.8 php-4.3.10 (Apache/1.3.33 static) zps
blitz, php_templates: so-модули, CFLAGS -g -O2

----------------------------------------------------------
N         Engine name          Time           Percentage  
----------------------------------------------------------
1            php              0.00045          100%
2            blitz            0.000834         185%
3            php_templates    0.001595         354%
4            smarty           0.001694         376%
5            str_replace      0.00373          829%
6            phemplate        0.004215         937%
7            fasttemplate     0.006139         1364%
8            vtemplate        0.008755         1946%
9            ultratemplate    0.012747         2833%
10           templatepower    0.018678         4151%
11           bugitemplate     0.019286         4286%
12           phplib           0.025478         5662%
13           profTemplate     0.045148         10033%
14           xtemplate        0.048137         10697%

Столь сильное отличие php от прочих движков объясняется тем, что весь код php-теста упакован в один файл, в то время для остальных есть два файла - php-файл контроллера и шаблонный файл, который парсится котроллером.

В результаты этого теста не включены некоторые известные шаблонные движки, такие как madtemplate, PEAR::Sigma и PEAR::HTML_Template_IT по простой причине: они не были установлены на тестовых машинах. Однако, насколько мне известно, эти проекты не являются кандидатами на попадание в пятерку лидеров. В этом можно убедиться, например, проведя онлайн-тесты самостоятельно, или скачав тестирущую программу.

Второй тест более приближен к полевым условиям. Он представляет собой тестирование некоторой динамической страницы, подготовленной с использованием разных движков, при помощи стардартной утилиты ab. Итак, у нас есть страница какого-то псевдо-портала, содержащая:
- ротирующиеся рекламные уши (3 шт)
- "полосатую" навигацию (~10 разделов)
- горячие новости (~10 шт)
- список пользователи онлайн (~20 шт)
- голосовалка с вариантами ответов (3 ответа)
- прочие переменные на странице (~5 шт)

Для тестирования было выбрано 4 подхода:

  • ugly php mess: используется только php, причем весь код полностью упакован в один файл, представляя собой эдакую "кашу". Такой прием практически никогда не встречается в реальных больших проектах, но включен в тесты исключительно ради интереса, посколько очевидно является самым быстрым.
  • php includes: используется только php, функционально разные блоки (элементы списков) вынесены в отдельные файлы
  • php_templates: один шаблон, на каждый функциональный блок - контекст
  • blitz includes: разные шаблоны на каждый функциональный блок, подключаемые через include
  • blitz ctx: один шаблон, на каждый функциональный блок - контекст
  • blitz ctx arrays: один шаблон, на каждый функциональный блок - контекст, один вызов установки массива итераций
  • blitzpack: разные шаблоны на каждый функциональный блок, упакованые в один blitzpack, подключаемые через include
  • smarty: один шаблон, на функциональные блоки - циклы внутри шаблона

    Все данные упакованы в структуру в отдельном файле, который инклюдится во всех тестовых вариантах. Числа запросов в секунду, которое выполняет сервер для каждого из методов, представлены в таблице 2.

    тбл. 2
    Тестовая машина(B), см тбл. 1

    
    ab -n20000 -c100, ZPS on
    ################################
    ugly php mess:         1150
    blitz ctx arrays:      890
    blitz ctx:             825
    php includes:          770
    blitzpack:             725
    blitz incudes:         680
    smarty:                620
    php_templates:         615
    
    ab -n20000 -c100, ZPS off
    ################################
    ugly php mess:         660
    blitz ctx arrays:      590
    blitz ctx:             560
    php_templates:         450
    blitzpack:             440
    blitz includes:        430
    php includes:          285
    smarty:                125
    
    
    К сожалению, я не имел возможности провести тесты для других шаблонных движков (впрочем, код для этого теста доступен, вы можете добавить в него решения исходной задачи с иcпользованием любых других средств). Поэтому ограничусь обобщенной интерпретацией этих результатов. То, что native PHP-код вместе с акселератором всегда будут быстрее прочих решений - очевидно. Правда, следует особенно подчеркнуть, что native в этом смысле - именно написанный программистом самим, а не "скомпилированный". В этом легко убедиться, заглянув внутрь любого "скомпилированного" шаблона: как правило, их код состоит из многомерных, довольно сложных для выполнения конструкций, значительно сложнее, чем написанный правильными руками код ;) Поскольку разница между blitz и "правильным" методом php includes не является кардинальной, а все синтетические тесты позволяют лишь выявить группы приблизительно равных, можно с определенной долей уверенности считать методы разработки с использованием php, blitz и php_templates примерно одинаковыми по производительности.

    Следует также принять во внимание, что в реальном проекте разница в скорости между различными методами скорее всего будет ещё меньше. Во-первых это связано с тем, что значительное время будет тратиться на работу с источниками данных (СУБД, различные сервисы и проч.). Во-вторых, отношение "количества" кода, относящегося к уровню представления, и прочего кода будет совершенно иным. Грубо говоря, view_code = full_code для тестов и пусть выигрыш на синтетическом тесте составляет даже десятки процентов. Но в реальном проекте часто выполняется соотношение view_code << full_code, и поэтому выигрыш на уровне представления уже почти ничего не даст. Как вы могли заметить, почти все тесты были проведены с использованием акселератора из ZPS. Вряд ли сейчас можно представить крупный проект, в котором не используется акселератор, однако, акселератор акселератору рознь. И вполне возможно вы получите совершенно иные результаты при использовании, например, eAccelerator'a. В-общем, призываю вас не полагаться полностью на приведенные результаты. Скачивайте тесты, экспериментируйте на реальных задачах, и выбирайте те решения, которые дают выигрыш в вашем проекте.

    Инсталляция

    Blitz - расширение PHP, поставляемое пока исключительно в исходных кодах, поэтому его инсталляция состоит из обычных шагов по сборке расширешия:
    bash> tar zxvf blitz.tar.gz
    bash> cd blitz
    bash> phpize
    bash> make
    bash> ./configure
    bash> make install
    

    Начиная с версии 0.4 в дистрибутив входит несколько тестов:

    bash>./run-tests.sh
    =====================================================================
    CWD         : /home/fisher/prj/blitz/tests
    PHP         : /usr/local/bin/php
    PHP_SAPI    : cli
    PHP_VERSION : 4.3.10
    ZEND_VERSION: 1.3.0
    PHP_OS      : Linux - Linux fisher 2.6.8-24.19-smp #1 SMP Tue Nov 29 14:32:45 UTC 2005 i686
    INI actual  : /usr/local/lib/php.ini
    More .INIs  :
    Extra dirs  :
    =====================================================================
    Running selected tests.
    PASS contexts [context.phpt]
    PASS errors and warnings [errors.phpt]
    PASS fetch [fetch.phpt]
    PASS complex fetch [fetch_cmplx.phpt]
    PASS predefined methods: if [if.phpt]
    PASS predefined methods: include [include.phpt]
    PASS user-defined methods [method.phpt]
    PASS method call from inner include [mfi.phpt]
    PASS variables [var.phpt]
    
    После этого вы, возможно, захотите отредактировать свой php.ini, включив blitz в список расширений:
    extension=blitz.so

    Сборка blitz тестировалась на Linux 2.6 (i386) и Windows XP (до ветки 0.4). Пользователи Windows могут воспользоваться готовыми Win32-бинарниками (версии ветки 0.4 пока недоступны).

    Вводный курс

    Как и во многих других шаблонных движках двумя базовыми сущностями проекта, которые использует blitz, являются собственно шаблон и объект, управляющий выполнением этого шаблона (контроллер шаблона). В шаблоне допускаются три вида конструкций
  • переменная, {{ $my_var }}
  • вызов спец-метода, {{ include('some_other_template.tpl') }} или {{ if($my_var,'00ffff','00ffee') }}
  • вызов пользовательского метода, {{ my_method }}
  • контксты (или подшаблоны), с неограниченной вложенностью, "итерируемые" из контроллера: {{ BEGIN root }} hello {{ BEGIN hi }}, world !{{ END }} {{ END }}

    Переменные. Следующий код демонстрирует работу с переменными:

    Ex. 1, file ex1A.tpl:
    Это некоторый тест для двух переменных: {{ $a }} и {{ $b }}, номер итерации: {{ $i }}
    
    Ex. 1, file ex1B.php
    <?
    
    $T = new Blitz('ex1A.tpl');
    $i = 0;
    $i_max = 10;
    for ($i = 0; $i<$i_max; $i++) {
        echo $T->parse(
            array(
                'a' => 'var_'.(2*$i),
                'b' => 'var_'.(2*$i+1),
                'i' => $i
            )
        );
    }
    
    ?>
    Ex.1 Output:
    Это некоторый тест для двух переменных: var_0 и var_1, номер итерации: 0
    Это некоторый тест для двух переменных: var_2 и var_3, номер итерации: 1
    Это некоторый тест для двух переменных: var_4 и var_5, номер итерации: 2
    Это некоторый тест для двух переменных: var_6 и var_7, номер итерации: 3
    Это некоторый тест для двух переменных: var_8 и var_9, номер итерации: 4
    Это некоторый тест для двух переменных: var_10 и var_11, номер итерации: 5
    Это некоторый тест для двух переменных: var_12 и var_13, номер итерации: 6
    Это некоторый тест для двух переменных: var_14 и var_15, номер итерации: 7
    Это некоторый тест для двух переменных: var_16 и var_17, номер итерации: 8
    Это некоторый тест для двух переменных: var_18 и var_19, номер итерации: 9
    
    
    Класс Blitz - внутренний класс расширения, управляющий шаблоном. Первый и единственный аргумент конструктора класса - имя шаблона. Вместо вызова parse($arr_params) вы можете использовать set($arr_params) и parse() без аргументов.

    Спец-методы. Следующий код демонстрирует использование include:

    Ex. 2, file ex2A.tpl:
    Мама {{ include('ex2B.tpl') }} раму
    
    Ex. 2, file ex2B.tpl:
    мыла
    
    Ex. 2, file ex2С.php:
    <?
    
    $T = new Blitz('ex2A.tpl');
    echo $T->parse();
    echo "\n";
    
    ?>
    Ex.2 Output:
    Мама мыла раму
    
    
    Если вы включаете один шаблон в другой, то включаемый шаблон наследует все переменные внешнего шаблона:
    Ex. 3, file ex3A.tpl:
    переменная a = {{ $a }}
    внутренний шаблон: {{ include('ex3B.tpl') }}
    переменная b = {{ $b }}
    
    Ex. 3, file ex3B.tpl:
    /* переменная a = {{ $a }}, переменная b = {{ $b }} */
    
    Ex. 3, file ex3С.php:
    <?
    
    $T = new Blitz('ex3A.tpl');
    $T->set(array('a' => 'a_value', 'b' => 'b_value'));
    echo $T->parse();
    echo "\n";
    
    ?>
    Ex.3 Output:
    переменная a = a_value
    внутренний шаблон: /* переменная a = a_value, переменная b = b_value */
    переменная b = b_value
    
    В шаблоне также можно использовать псевдо-оператор if. На самом деле, это такой же спец-метод, отображающий в зависимости от истинности предиката либо один аргумент, либо другой. Cуществует укороченная форма if, без третьего аргумента - аналогичная полной форме с пустым третьим аргументом: if($a,$b) = if($a,$b,'');
    Ex. 4, file ex4A.tpl:
    {{ $num }}. {{ $name }} {{ if($rip,'[R.I.P.]') }}
    
    Ex. 4, file ex4B.php:
    <?
    
    T = new Blitz('ex4A.tpl');
    $character = array(
        array(1,'The Dude',0),
        array(2,'Walter Sobchak',0),
        array(3,'Donny',1),
        array(4,'Maude Lebowski',0),
        array(5,'The Big Lebowski',0),
        array(6,'Brandt',0),
        array(7,'Jesus Quintana',0),
    );
    
    foreach ($character as $i => $data) {
       echo $T->parse(
           array(
               'num' => $data[0],
               'name' => $data[1],
               'rip' => $data[2]
           )
       );
    }
    
    ?>
    Ex.4 Output:
    1. The Dude
    2. Walter Sobchak
    3. Donny [R.I.P.]
    4. Maude Lebowski
    5. The Big Lebowski
    6. Brandt
    7. Jesus Quintana
    
    В данном примере использована укороченная форма if.

    Пользовательские методы. Возможность включать в шаблон пользовательские методы - самая интересная с точки зрения организации хорошего и удобно читаемого кода. До сих пор в примерах использовался стандартный класс Blitz, никакими новыми методами не обладающий. Однако, если создать объект класса-наследника Blitz, который предоставляет некоторый метод my_test, в шаблоне можно использовать вызов этого метода ровно с таким же названием:

    Ex 5, file ex5a.tpl:
    пример вызова пользовательского метода: {{ my_test }}
    
    Ex 5, file ex5B.php:
    <?
    
    class BlitzTemplate extends Blitz {
    
        function BlitzTemplate($t) {
            parent::Blitz($t);
        }
    
        function my_test() {
            return 'user method called ('.__CLASS__.','.__LINE__.')';
        }
    }
    
    $T = new BlitzTemplate('ex5A.tpl');
    echo $T->parse();
    
    ?>
    
    Ex 5, Output:
    пример вызова пользовательского метода: user method called (blitztemplate,10)
    

    Все, что возвращает пользовательский метод будет сконвертировано в строку и подставлено вместо вызова. Если вызов метода в шаблоне есть, но самого метода нет - будет подставлена пустая строка. Вообще, действует обычное правило: никаких исходных вызовов никогда не присутсвует в конечном результате, независимо от существования переменной, метода и проч.

    Внутри пользовательского метода также можно включать другие шаблоны. Конечно, никто не запрещает вам написать что-нибудь вроде:

    class BlitzTemplate extends Blitz {
        var $data;
        var $TItem;
        function BlitzTemplate($t,$titem) {
            parent::Blitz($t);
            $TItem = new Blitz($titem);
        }
    
        function set_data() {
            // some code
        }
    
        function my_test() {
            $result = '';
            foreach ($this->data as $i_data) {
                $result .= $TItem->parse($i_data);
            }
            return $result;
        }
    }
    
    $T = new BlitzTemplate('main.tpl','item.tpl');
    // $bla_bla = ...
    $T->set_data($blabla);
    echo $T->parse();
    
    

    Этот метод будет работать, но не очень хорош по двум причинам. Во-первых, $TItem является совершенно отдельным объектом, никак не связанным с $T. Blitzу несколько сложнее переключаться с одного объекта на другой, нежели выполнять все операции через один и тот же объект. Во-вторых, $TItem не будет наследовать установленные переменные из $T, их при необходимости нужно будет протягивать самостоятельно, а также внутри $TItem нельзя использовать методы $T. Поэтому более правильным будет использование встроенного метода include:

    Ex.6, file ex6A.tpl:
    parent value: {{ $parent_val }}
    child_value: {{ $child_val }}
    ===========================================================
    {{ test_include }}
    ===========================================================
    parent value: {{ $parent_val }}
    child_value: {{ $child_val }}
    
    Ex.6, file ex6B.tpl:
    parent method: {{ my_test }}
    child value: {{ $child_val }}
    parent value: {{ $parent_val }}
    
    Ex.6, file ex6C.php:
    <?
    
    class BlitzTemplate extends Blitz {
        var $titem;
    
        function BlitzTemplate($t,$titem) {
            parent::Blitz($t);
            $this->set(array('parent_val' => 'some_parent_val'));
            $this->titem = $titem;
        }
    
        function my_test() {
            return 'user method called ('.__CLASS__.','.__LINE__.')';
        }
    
        function test_include() {
            $result = '';
            while($i++<3) {
                $result .= $this->include($this->titem,array(
                    'child_val' => 'i_'.$i
                ));
            }
            return $result;
        }
    
    }
    
    $T = new BlitzTemplate('ex6A.tpl','ex6B.tpl');
    echo $T->parse();
    
    ?>
    
    Ex.6, Output:
    parent value: some_parent_val
    child_value:
    ===========================================================
    parent method: user method called (blitztemplate,13)
    child value: i_1
    parent value: some_parent_val
    parent method: user method called (blitztemplate,13)
    child value: i_2
    parent value: some_parent_val
    parent method: user method called (blitztemplate,13)
    child value: i_3
    parent value: some_parent_val
    
    ===========================================================
    parent value: some_parent_val
    child_value: i_3
    
    При первой обработке шаблона структура всех его тэгов сохраняется, поэтому при последующих вызовах шаблон снова не анализируется. Обратите внимание на то, что до выполнения метода test_include переменная child_value пуста и не "видна" в шаблоне, но после выполнения видна и содержит последнее установленное значение. Это поведение аналогично тому, что поисходит при выполнении php-кода, если бы вместо test_include у нас был include некоторого php-файла, внутри которого бы инициализировалась новая переменная. Внутри внешнего кода до include она имела бы неопределенное значение, но после - уже нет. На самом деле при вызове include сначала все параметры вызова include добавляются к уже установленным параметрам шаблона, и уже после этого происходит выполнение кода, поэтому ничего удивительного в таком поведении нет. Эту особенность следует иметь ввиду, чтобы случайно не "затереть" ранее установленную переменную.

    Начиная с версии 0.3 в blitz поддерживается передача параметров из шаблона в пользовательский метод:

    Ex.7, file ex7A.tpl:
    calling template with arguments: {{ my_test(134,$x,"hello,world!",$dummy) }}
    Ex.7, file ex7B.php:
    
    <?
    
    class BlitzTemplate extends Blitz {
        var $titem;
    
        function BlitzTemplate($t) {
            parent::Blitz($t);
            $this->set(array('x' => 1234));
        }
    
        function my_test($p1,$p2,$p3,$p4) {
            $result = 'user method called ('.__CLASS__.','.__LINE__.')'."\n";
            $result .= 'parameters are:'."\n";
            $result .= '1:'.var_export($p1,TRUE)."\n";
            $result .= '2:'.var_export($p2,TRUE)."\n";
            $result .= '3:'.var_export($p3,TRUE)."\n";
            $result .= '4:'.var_export($p4,TRUE)."\n";
            return $result;
        }
    }
    
    $T = new BlitzTemplate('ex7A.tpl');
    echo $T->parse();
    
    ?>
    
    Ex. 7, Output:
    calling template with arguments: user method called (blitztemplate,12)
    parameters are:
    1:134
    2:1234
    3:'hello,world!'
    4:NULL
    
    

    Контексты. Начиная с версии 0.4 в blitz практически без изменений была добавлена функциональность движка php_templates. Суть контекстов заключается в следующем. Обычно мы имеем дело с "плоскими" шаблонами, в них есть либо переменные, либо какие-то операторы или вызовы, которые обязательно исполняются. Контексты - это подшаблоны, которые не используются до тех пор, пока контроллер шаблона явно это не укажет. Например, если у нас есть php-код " переменная: [?=$a; ?], метод: [?=b(); ?] ", то оба куска этого кода будут всегда исполнены. Никакой иерархии нет, все php-шаблоны плоские. Теперь рассмотрим некий псевдо-код с контекстами "переменная : {{ $a }}, контекст {{ BEGIN b }} что-то внутри {{ END }}". Здесь b - это контекст, который по умолчанию не будет использован (чаще используют термин "итерирован") - и вместо кода от BEGIN до END ничего не будет. Если контекст итерирован один раз, то в шаблоне появится результат исполнения внутренней части контекста. Если проитерировать дважды - результат исполнения внутренней части контекста два раза. Параметры у каждой итерации разумеется могут быть разными. Таким образом, для отображения списков достаточно просто итерировать контекст, описывающий элемент списка, для каждого элемента. Самое удобное заключается в том, что контексты могут быть вложены друг в друга. Каждый контекст однозначно определён своим путём - /root/node/item одначает что есть контекст root внутри которого контекст node внутри которого item. Если итерировать родительский контекст с одними параметрами, потом вложенные конкетсты, потом снова родительские контексты с другими параметрами, а потом снова вложенные, то можно при помощи одного единственного шаблона сделать страницу абсолютно любой сложности. Есть базовые операции с контекстами - установить текущий контекст и итерировать контекст. Установка означает, что все вызовы по умолчанию работают с этим контекстом, тут есть хорошая аналогия с работой коммандной оболочке - установить текущий контекст в /root/node/item по смыслу то же самое что сделать cd в /root/node/item. А итерировать фактически означает "исполнить".

    Следующий код выводит ужасно доставшее всех знакомое всем приветствие, запрятанное в трёх вложенных контекстах.

    tree.tpl:
    {{ BEGIN root }}
      {{ BEGIN node }}
        {{ BEGIN item }}
            hello, world
        {{ END }}
      {{ END }}
    {{ END }}
    код:
    $T = new Blitz('tree.tpl');
    $T->iterate('/root/node/item');
    echo $T->parse();
    

    Следующий пример демонстрирует работу с простыми списками

    ex8A.tpl:
    {{ BEGIN row }}row #{{ $i }}
    {{ END }}
    ex8B.php:
    $T = new Blitz('ex8A.tpl');
    
    $max_num_list = 5;
    
    // use context & iterate
    $T->context('row');
    for($i=0; $i<$max_num_list; $i++) {
        $T->iterate();
        $T->set(array('i' => $i));
    }
    
    // or just use block
    for($i=0; $i<$max_num_list; $i++) {
        $T->block('/row',array('i' => $i));
    }
    
    echo $T->parse();
    
    Ex. 8, Output:
    row #0
    row #1
    row #2
    row #3
    row #4
    row #0
    row #1
    row #2
    row #3
    row #4
    

    Метод block - удобная замена последовательным iterate и set, которые встречаются в коде очень часто именно вместе. Разумеется, в Blitz внутри контекста можно использовать и include, и if, и пользовательские методы. Переменные, установленные в родительских контекстах, "не видны" в дочерних. Если есть необходимость в глобальных переменных, которые будут "видны" в любом месте шаблона - можно использовать метод set_globals вместо set.

    Следующий пример показывает, как при помощи вложенных конекстов строить более сложные списки.

    ex9A.tpl:
    complex list example
    {{ BEGIN list; }}
    list #{{ $list_num }}
    {{ BEGIN list_empty; }} this list is empty {{ END }}{{ BEGIN list_item; }} row #{{ $i_row; }}
    {{ END }}
    {{ END }}
    ex9B.php:
    $T = new Blitz('ex9A.tpl');
    
    $max_num_list = 5;
    $max_num_item = 5;
    
    $T->context('/list');
    for($i=0; $i<$max_num_list; $i++) {
        $T->block('',array('list_num' => $i));
        $is_empty = $i%2; // emulate empty sub-lists
    
        if($is_empty) {
            $T->block('list_empty');
        } else {
            for($j=0; $j<$max_num_item; $j++) {
               $T->block('list_item',array('i_row' => $i.':'.$j));
            }
        }
    }
    
    echo $T->parse();
    
    Ex. 9, Output:
    complex list example
    
    list #0
     row #0:0
     row #0:1
     row #0:2
     row #0:3
     row #0:4
    
    
    list #1
     this list is empty
    
    list #2
     row #2:0
     row #2:1
     row #2:2
     row #2:3
     row #2:4
    
    
    list #3
     this list is empty
    
    list #4
     row #4:0
     row #4:1
     row #4:2
     row #4:3
     row #4:4
    

    Фактически на это простом механизме попеременных итераций вложенных котекстов реализуется абсолютно любая логика даже в одном единственном шаблоне.

    Для того, чтобы иметь возможность разделять шаблоны, и использовать контент одного шаблона при работе с другим шаблоном, и даже более того, тело одного контекста в одном шаблоне внутри другого, можно использовать метод fetch:

    ex10A.tpl:
    {{ BEGIN online }} online! {{ END }}
    {{ BEGIN offline }} was online {{ $n }} {{ BEGIN h }}hours{{ END }}{{ BEGIN d }}days{{ END }}{{ BEGIN m }}months{{ END }} ago {{ END }}
    {{ BEGIN away }} away... {{ END }}
    
    ex10B.php:
    $T = new Blitz('ex10A.tpl');
    
    // online
    echo $T->fetch('online')."\n";
    
    // away
    echo $T->fetch('away')."\n";
    
    $T->context('offline');
    
    // 15 days ago
    $T->iterate('d');
    echo $T->fetch('offline', array('n' => 15))."\n";
    
    $T->iterate(); // create next iteration for offline block
    
    // 2 months ago
    $T->iterate('m');
    echo $T->fetch('offline', array('n' => 2))."\n";
    
    Ex. 10, Output:
     online!
     away...
     was online 15 days ago
     was online 2 months ago
    

    Вместо того, чтобы "очистить" каким-либо образом предыдущую итерацию контекста offline (такой функционал будет добавлен в blitz в самое ближайшее время), создается новая итерация. Метод fetch получает результат исполнения последней итерации контекста. В-общем, функционал контекстов достаточно мощный для того, что бы использовать шаблонный движок для сколь угодно сложных проектов. В blitz внутри контектов также доступны такие приятные мелочи как if, include и вызов метода контроллера.

    Наконец, можно использовать метод load, чтобы загрузить тело шаблона из обычной php-переменной:

    ex11A.php:
    $body = <<<BODY
    hello, {{ \$who }}!
    {{ bye; }}
    
    BODY;
    
    class View extends Blitz {
        function bye() {
            return "Have a lot of fun!...";
        }
    }
    
    $T = new View();
    $T->load($body);
    echo $T->parse(array('who' => 'world'));
    
    Ex. 11, Output:
    hello, world!
    Have a lot of fun!...
    

    Параметры настройки

    Вы можете изменять следующие параметры настройки (php.ini):
  • строка, открывающая тэг (blitz.tag_open), по умолчанию равный '{{'
  • строка, закрывающая тэг (blitz.tag_close), по умолчанию равный '}}'
  • префикс для переменных (blitz.var_prefix), по умолчанию равный '$'

    Синтаксис Blitz и API

    under construction ;)