Blitz родился весьма неоригинально, for fun. Однако, поигравшись с ним
немного, мне показалось, что скорость, с которой он работает, и удобства,
которые он предоставляет разработчику - стоят того, чтобы дать его поиграться
коллегам. Основных "фишек" у Blitz три:
- написан как PHP-модуль на Си, и является одним из самых быстрых движков
- имеет простой и интуитивно понятный синтаксис
- позволяет структурировать код удобным и легко читаемым образом
Blitz поддерживает разделение и скрытие функционально различных частей шаблонов с помощью простого механизма: текст шаблона может содержать вызов пользовательского метода объекта, который этим шаблоном управляет. Таким образом достигается основная цель: шаблон не содержит большого количества блоков и контекстов, часто мешающих разобраться, что к чему. Напротив, даже в проекте со сложной логикой представления при правильном подходе шаблоны будут давать разработчику своеобразную "карту" всего проекта. Blitz также позволяет включать одни шаблоны в другие (аналог include) и поддерживает условный вывод переменных (аналог if).
Мне бы не хотелось здесь следовать академической традиции и проводить подробный анализ
других проектов. В любом случае, если вы научились эффективно использовать сам PHP в качестве
шаблонного движка, обходясь без сторонних продуктов и библиотек - вы счастливый человек.
Вы используете самый эффективный с точки зрения производительности подход, и если он вам
удобен - придерживайтесь его. Если нет - попробуйте Blitz. Возможно, он вас приятно удивит ;)
С сожалению, мне неизвестна ни одна простая, универсальная и по-настоящему корректная методика анализа
производительности шаблонных движков. А результатами любых искуственных, или как их
ещё принято называть, синтетических, тестов пользоваться нужно максимально осторожно.
Тем не менее, здесь приводятся результаты двух тестов. Первый тест - классический,
измеряющий скорость выполнения циклических итераций одного и того же шаблона.
Тест крайне простой, но позволяющий достаточно условно разделить группы движков
на "нормальные", "медленные" и "никуда не годные". Число итераций и переменных в блоке
было взято по умолчанию (9 переменных, 50 итераций), результаты этого теста приведены
в таблице 1. Как легко видеть, Blitz по крайней мере аутсайдером не является.
тбл. 1
Тестовая машина(B): PC PIV 2,8GHz (HT off) 1GB; linux-2.6.8 php-4.3.10 (Apache/1.3.33 static) zps
В результаты этого теста не включены некоторые известные шаблонные движки,
такие как madtemplate, PEAR::Sigma и PEAR::HTML_Template_IT по простой причине: они
не были установлены на тестовых машинах. Однако, насколько мне
известно, эти проекты не являются кандидатами на попадание
в пятерку лидеров. В этом можно убедиться, например, проведя
онлайн-тесты самостоятельно,
или скачав тестирущую программу.
Второй тест более приближен к полевым условиям. Он представляет собой тестирование
некоторой динамической страницы, подготовленной с использованием разных движков, при помощи
стардартной утилиты ab. Итак, у нас есть страница какого-то псевдо-портала, содержащая:
Для тестирования было выбрано 4 подхода:
Все данные упакованы в структуру в отдельном файле, который инклюдится во всех тестовых вариантах.
Числа запросов в секунду, которое выполняет сервер для каждого из методов, представлены в таблице 2.
тбл. 2 Следует также принять во внимание, что в реальном проекте
разница в скорости между различными методами скорее всего будет ещё меньше.
Во-первых это связано с тем, что значительное время будет тратиться на работу с
источниками данных (СУБД, различные сервисы и проч.). Во-вторых, отношение "количества"
кода, относящегося к уровню представления, и прочего кода будет совершенно иным. Грубо
говоря, view_code = full_code для тестов и пусть выигрыш на синтетическом
тесте составляет даже десятки процентов. Но в реальном проекте часто выполняется
соотношение view_code << full_code, и поэтому выигрыш на уровне представления
уже почти ничего не даст. Как вы могли заметить, почти все тесты были проведены
с использованием акселератора из ZPS. Вряд ли сейчас можно представить крупный проект,
в котором не используется акселератор, однако, акселератор акселератору рознь.
И вполне возможно вы получите совершенно иные результаты при использовании, например, eAccelerator'a.
В-общем, призываю вас не полагаться полностью на приведенные результаты.
Скачивайте тесты, экспериментируйте на реальных задачах, и выбирайте те решения, которые дают
выигрыш в вашем проекте.
Сборка blitz тестировалась на нескольких *nux-платформах, и пока случаев, чтобы
были какие-то проблемы при сборке, неизвестно ;) Под windows blitz пока не собирался.
Переменные. Следующий код демонстрирует работу с переменными:
Спец-методы. Следующий код демонстрирует использование include:
Пользовательские методы. Возможность включать в шаблон пользовательские методы -
самая интересная с точки зрения организации хорошего и удобно читаемого кода. До сих пор
в примерах использовался стандартный класс Blitz, никакими новыми методами не обладающий.
Однако, если создать объект класса-наследника Blitz, который предоставляет некоторый
метод my_test, в шаблоне можно использовать вызов этого метода ровно с таким же названием:
Все, что возвращает пользовательский метод будет сконвертировано в строку и подставлено вместо вызова.
Если вызов метода в шаблоне есть, но самого метода нет - будет подставлена пустая строка.
Вообще, действует обычное правило: никаких исходных вызовов
никогда не присутсвует в конечном результате, независимо от существования переменной,
метода и проч.
Внутри пользовательского метода также можно включать другие шаблоны. Конечно,
никто не запрещает вам написать что-нибудь вроде:
Этот метод будет работать, но не очень хорош по двум причинам. Во-первых, $TItem является совершенно
отдельным объектом, никак не связанным с $T. Blitzу несколько сложнее переключаться с одного объекта
на другой, нежели выполнять все операции через один и тот же объект. Во-вторых, $TItem не будет наследовать
установленные переменные из $T, их при необходимости нужно будет протягивать самостоятельно,
а также внутри $TItem нельзя использовать методы $T.
Поэтому более правильным будет использование встроенного метода include:
Синтаксис шаблонов
Тэги шаблонов blitz имеют обобщенный вид [TAG_OPEN][INNER_CODE][TAG_CLOSE].
TAG_OPEN,TAG_CLOSE - строки, открывающие и закрывающие тэг, по умолчанию '{{' и '}}'.
INNER_CODE может содержать лексемы:
Пустые символы (пробелы и симболы табуляции) от конца
открывающего тэга до первого непустого символа и от последнего непустого символа до конца
закрывающего тэга анализатором пропускаются, в результат не включаются. Общая длина
лексемы INNER_CODE с учетом пустых символов не должна превышать BLITZ_MAX_LEXEM_LEN=1024 символа.
Возможно, в последующих релизах эти ограничения будут изменены или вовсе сняты.
Пример:
Класс Blitz
Конструктор: Методы:
set(ARRAY), установить значения переменных шаблона. ARRAY - хэш имя_переменной => значение.
В случае ошибки возвращает FALSE, в остальных случаях - TRUE.
parse([ARRAY]), выполнить шаблон. ARRAY - необязателтьный параметр, по смыслу тот же
хэш имя_переменной => значение, что и в методе set (параметр используется для
более компактного вызова, минуя set). Возвращает либо переменную, содержащую результат выполнения шаблона, либо FALSE.
include(TEMPLATE_NAME,[ARRAY]), выполнить другой шаблон TEMPLATE_NAME с параметрами ARRAY.
Перед выполнением осуществляется объединение уже установленных переменных шаблона с параметрами ARRAY,
код вызываемого шаблона "наследует" все переменные родительского шаблона и может содержать
вызовы методов родительского шаблона. Возвращает либо переменную,
содержащую результат выполнения шаблона, либо FALSE.
Пример (Ex.6):
Некоторые результаты тестов производительности
Тестовая машина(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%
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%
- ротирующиеся рекламные уши (3 шт)
- "полосатую" навигацию (~10 разделов)
- горячие новости (~10 шт)
- список пользователи онлайн (~20 шт)
- голосовалка с вариантами ответов (3 ответа)
- прочие переменные на странице (~5 шт)
Тестовая машина(B), см тбл. 1
ab -n20000 -c256
----------------------------------------------------------
ZPS on ZPS off
----------------------------------------------------------
php mess: 970 620
php includes: 675 280
blitz: 620 410
php_templates: 570 430
К сожалению, я не имел возможности провести тесты для других шаблонных движков
(впрочем, код для этого теста доступен,
вы можете добавить в него решения исходной задачи с иcпользованием любых других средств).
Поэтому ограничусь обобщенной интерпретацией этих результатов. То, что native PHP-код вместе
с акселератором всегда будут быстрее прочих решений - очевидно. Правда, следует особенно
подчеркнуть, что native в этом смысле - именно написанный программистом самим,
а не "скомпилированный". В этом легко убедиться, заглянув внутрь любого
"скомпилированного" шаблона: как правило, их код состоит из многомерных,
довольно сложных для выполнения конструкций, значительно сложнее, чем написанный
правильными руками код ;) Поскольку разница между blitz и "правильным" методом
php includes не является кардинальной, а все синтетические тесты позволяют лишь
выявить группы приблизительно равных, можно с определенной долей уверенности считать методы
разработки с использованием php, blitz и php_templates примерно одинаковыми
по производительности.
Инсталляция
Blitz - расширение PHP, поставляемое пока исключительно в исходных кодах,
поэтому его инсталляция состоит из обычных шагов по сборке расширешия:
bash> tar zxvf blitz.tar.gz
bash> cd blitz
bash> phpize
bash> make
bash> make install
После этого вы, возможно, захотите отредактировать свой php.ini, включив blitz в список расширений:
extension=blitz.so
Вводный курс
Как и во многих других шаблонных движках двумя базовыми сущностями проекта,
которые использует blitz, являются собственно шаблон и объект, управляющий
выполнением этого шаблона.
В шаблоне допускаются три вида конструкций
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() без аргументов.
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.
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();
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 добавляются
к уже установленным параметрам шаблона, и уже после этого происходит выполнение кода,
поэтому ничего удивительного в таком поведении нет. Эту особенность следует иметь ввиду,
чтобы случайно не "затереть" ранее установленную переменную.
Параметры настройки
Вы можете изменять следующие параметры настройки (php.ini):
Синтаксис Blitz и API
переменная: {{ $a }}
переменная: {{ $b }}
вставка шаблона: {{ include("my.tpl") }}
вызовы метода if:
{{ if(77,'test if') }}
{{ if(88,"test if") }}
{{ if('','test if') }}
{{ if($a,'test\' if') }}
{{ if($a,6123) }}
{{ if(77," \"test if",'test if') }}
{{ if('hello','test if','test if') }}
{{ if($a,'test if3','test if') }}
{{ if($a,1789,'test if3') }}
{{ if($a,1789,$b) }}
{{ if($b,$c,1789) }}
{{ if($d,'test if',$a) }}
вызов динамического метода: {{ some_method }}
Blitz(TEMPLATE_NAME), TEMPLATE_NAME - полный путь до файла шаблона
<?
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();
?>