О наболевшем, или не Smarty единым

Последнее время мне приходилось проводить собеседования с претендентами на должность php-программистов. Беседуя с кандидатами, заметил, что когда речь заходит о шаблонизаторах (термин, ИМХО, несколько коряв, но поскольку прочно прижился в веб-сообществах позвольте его использовать) все неизменно называют одно единственное слово – Smarty. Причем мнения о Smarty бывают диаметрально противоположные от: “Знаю, активно использую”, до: “Ненавижу эту гадость”. Аналогичная картина наблюдается на, посещаемых мною, интернет-форумах. В умах подавляющего большинства разработчиков слова Smarty и шаблонизатор – синонимы. Надо ли говорить, что на самом деле это не так? В добавок к сложившемуся в головах стереотипу “шаблонизатор это Smarty”, у меня еще создалось впечатление, что многие начинающие разработчики вообще не понимают зачем именно им, именно в конкретном проекте нужен шаблонный парсер и их выбор архитектуры базируется на одной из двух, витающих по интернету, мантр: “настоящий профессионал всегда пользуется шаблонизатором для отделения логики от представления” или “php сам по себе шаблонизатор и незачем изобретать велосипед”. На самом деле и то и другое верно только отчасти и не в коем случае, не следует данные мантры принимать как истину в последней инстанции. А вот как точку, отсчета мы их можем принять и постараться рассмотреть по возможности отстраненно. Данный цикл статей преследует следующие цели:

  1. Постараться разобраться во всех тех неоднозначностях и непониманиях, что царят вокруг шаблонизаторов. В т.ч. развенчать “догмы” и “стереотипы”. :)
  2. Предложить конкретные решения и подходы и сделать введение в два продукта – XTemplate и Blitz, про которые практически нет никакой информации, кроме официальной документации (увы, практика показала, что начинающий программист уже на этапе продирания сквозь документацию отказывается от идеи использовать данный продукт, из-за банального непонимания).

XTemplate – шаблонный движок для “маленьких”

Прежде чем, пускаться в теоретические дебри и рассуждения, рассмотрим готовый продукт – шаблонизатор XTemplate, который можно прямо сейчас скачать (http://www.phpxtemplate.org/XTemplateDownloads) и прямо сейчас использовать. Это одно из крайне простых решений, которое, тем не менее, содержит все необходимое для создания более-менее стройной архитектуры средне-статистического веб–сайта. Основные его приемущества заключаются в следующем:

  1. Маленький размер. Для работы требуется всего один небольшой php-файл.
  2. Крайне простой синтаксис шаблонов, на изучение которого уйдут считанные минуты.
  3. Написан на PHP и не использует никаких дополнительных расширений, т.е. вы 100% сможете его использовать везде, где будет работать или отлаживаться ваш проект. (Лично меня всегда удивляло, что для огромного количество разработчиков именно этот пункт является решающим фактором, но факт есть факт).
  4. Имеет “блочную идеологию” (В чем именно ее преимущество мы подробнее рассмотрим позднее).

Для работы с XTemplate надо скопировать файл xtemplate.class.php, в удобное место в файловой структуре вашего проекта (ну например, в директорию libs), после чего подключить движок и создать объект парсера (php скрипт в котором производим все манипуляции над шаблоном, в дальнейшем будем называть контроллером):

include('libs/xtemplate.class.php'); // Подключаем файл
$xtpl = new XTemplate('templates/test.xtpl'); // Создаем объект

test.xtpl – собственно файл шаблона (их для удобства мы будем хранить в директории templates). Синтаксис шаблонов XTemplate представлен всего двумя основными конструкциями. Первой и основной является блок. Блоки имеют следующий вид:

<!-- BEGIN:block -->
Содержимое блока
<!-- END:block -->

Каждой инструкции открывающей блок <!-- BEGIN:block --> должна обязательно соответствовать закрывающая инструкция <!-- END:block -->, где “block” имя блока, которое вы можете давать по своему усмотрению исходя из контекста содержимого блока, коим может быть любой HTML код. Про блоки надо понять три вещи:

  1. Содержимое блока никаким образом не будет отображено, пока в контролере явно не будет указано это сделать. Данная операция называется парсинг блока и выполняется командой $xtpl->parse(‘имя блока’); (В принципе, есть возможность задать вывод по умолчанию для не пропарсенных блоков с помощью функции set_null_block)
  2. Содержимое блока тиражируется, столько раз, сколько выполнена команда parse. Выглядит это следующим образом — представьте, что при первом вызове parse содержимое блока отображается в шаблоне, а каждом последующем вы как будто выполняете операции его выделения – копирования – вставки, снизу от предыдущего отображения.
  3. Блоки могут вкладываться друг в друга, при этом парсинг осуществляется по принципу от внутренних блоков к внешним. т.е. шаблон разворачивается как бы изнутри.

Пришло время, проиллюстрировать вышесказанное примером. Итак, пусть шаблон имеет вид:

Шаблон:

<!-- BEGIN:index -->
    <!-- BEGIN:block -->
    <p>Это содержимое блока</p>
    <!-- END:block -->
<!-- END:index -->

Контроллер:

include('libs/xtemplate.class.php'); // Подключаем файл
$xtpl = new XTemplate('templates/test.xtpl'); // Создаем объект
for($i=0; $i<3; $i++)
{
    $xtpl->parse('index.block'); // Парсим блок "block" три раза в цикле
}
$xtpl->parse('index'); // Парсим блок "index"
$xtpl->out('index');

Результат вывода:

<p>Это содержимое блока</p>
<p>Это содержимое блока</p>
<p>Это содержимое блока</p>

Обратите внимание, блок “block” вложен в блок “index”. Для доступа к внутренним блокам в качестве разделителя используется точка. Таким образом, парсинг внутреннего блока осуществляется командой $xtpl->parse('index.block');

Команда out выводит результат распарсенного блока на печать. Также есть команда text, ее отличие в том, что результат парсинга просто возвращается. Т.е. можно писать:

$result = $xtpl->text('index');
Делаем что-то с  $result
echo $result;

Это бывает необходимо, когда с результатом парсинга надо проделать те или иные действия, например, сохранить пропарсенный шаблон в кэше.

Второй основной конструкцией синтаксиса XTemplate является тег, имеющий следующий синтаксис:

{value}

Аналогично блоку, пока тегу не будет явно присвоено значение с помощью функции assign, никакого вывода в шаблон осуществляться не будет (В принципе, есть возможность задать вывод по умолчанию для не присвоенных блоков с помощью функции set_null_string). Фактически тег – это переменная, значение которой устанавливается в котроллере командой assign. Для простоты понимания, представьте, что {value} это примерно тоже самое, что <?php echo $value; ?>
Поправим наш пример так, чтоб в парсинге блока участвовала переменная – счетчик пропарсенных блоков:

Шаблон:

<!-- BEGIN:index -->
    <!-- BEGIN:block -->
    <p>{value}. Это содержимое блока </p>
    <!-- END:block -->
<!-- END:index -->

Контроллер (инициализация и финальный вывод опущены, аналогично будет и в дальнейшем):

for($i=1; $i<=3; $i++)
{
    $xtpl->assign('value', $i); // Устанавливаем тегу value значение $i
    $xtpl->parse('index.block');
}
$xtpl->parse('index');

Результат вывода:

<p>1. Это содержимое блока</p>
<p>2. Это содержимое блока</p>
<p>3. Это содержимое блока</p>

С помощью команды assign можно устанавливать сразу целый массив, причем элементы массивов могут быть в свою очередь опять массивами. Цепочка вложенных ключей массива в теге отделяется точками.

Например:

Шаблон:

<!-- BEGIN:index -->
<p>{value.key1}</p>
<p>{value.key2.key2_1}</p>
<p>{value.key2.key2_2}</p>
<!-- END:index -->

Контроллер:

$arr = array // Инициализируем массив сложной структуры
(
    'key1' => 'Значение 1',
    'key2' => array
    (
        'key2_1' => 'Значение 2_1',
        'key2_2' => 'Значение 2_2'
    )
);
$xtpl->assign('value', $arr); // Одним присваиванием передаем массив в шаблон
$xtpl->parse('index');

Результат вывода:

<p>Значение 1</p>
<p>Значение 2_1</p>
<p>Значение 2_2</p>

Что касается синтаксиса, то XTemplate позволяет переопределить основные его элементы. В начале файла xtemplate.class.php легко найти следующие параметры:

public $block_start_delim = '<!-- ';
public $block_end_delim = '-->';
public $block_start_word = 'BEGIN:';
public $block_end_word = 'END:';
public $tag_start_delim = '{';
public $tag_end_delim = '}';

Надо ли переопределять данные параметры – решайте сами. Смысла особого в этом нет. Сомнения вызывают лишь фигурные скобки, которые могут встретится внутри тегов <script> или <style>, однако XTemplate обрабатывает такие ситуации корректно. По все видимости именем тега, заключенного в скобки, может служить лишь простой набор символов, соответствующий валидным именам переменных php.

Реальный проект, как правило, имеет сложною систему шаблонов, части которых могут повторятся. Для включения шаблона в шаблон используется конструкция {FILE «inside.xtpl»}. Имя включаемого файла может быть динамическим и подключаться с помощью функции assign_file следующим образом:

Шаблон:

<!-- BEGIN:index -->
<p>Это тект во внешнем шаблоне</p>
{FILE {inside}}
<p>Это тект тоже во внешнем шаблоне</p>
<!-- END:index

Второй шаблон (inside.xtpl):

<p>Это тект во внутреннем шаблоне</p>

Контроллер:

$xtpl->assign_file('inside', 'templates/inside.xtpl');

Результат вывода:

<p>Это тект во внешнем шаблоне</p>
<p>Это тект во внутреннем шаблоне </p>
<p>Это тект тоже во внешнем шаблоне</p>

Ну и напоследок упомяну еще об одной фишке. XTemplate также предоставляет возможность “рекурсивного” парсинга с помощью функции $xtpl->rparse(‘index’); Рекурсивного в кавычках, потому что реальной рекурсии здесь нет, просто при вызове данной функции будут один раз распарсены все внутренние блоки блока index. Честно говоря, реального применить этот функционал мне нигде не удалось – за не надобностью.

Вот вкратце, основные возможности XTemplate. Не смотря на простоту и лаконичность, на самом деле, присутствует все необходимое для построения HTML-ля любой сложности. Также можно порекомендовать посмотреть и погонять примеры кода, которые включены в скачиваемый дистрибутив.

О шаблонных механизмах. Блоки наше все!

Когда мы на примере XTemplate рассмотрели принципы работы блочного шаблонизатора, давайте вернемся к тому, с чего начали. Собственно, к вопросу: “Какую вообще роль играет в нашем проекте шаблонизатор?”. Типовой ответ начинающего программиста: “Отделение логики от представления” или “Отделение программного кода от HTML”. А вот теперь стоп! Давайте разбираться и с тем и другим. Сначала разбираемся с логикой. Проблема в том, что логику нашего приложения можно разделить в свою очередь на две части:

  1. Бизнес-логика т.е. извлечение данных и БД, их специфическая обработка, математически расчеты и.т.п.
  2. Вью-логика т.е. собственно оборачивание в HTML теги

Так какую логику мы хотим отделить от представления? Обе? Вряд ли т.к. тогда, возникает законный вопрос: “А что есть представление?”. Ага, видимо под словом “представление” и имеется ввиду Вью-логика. Итак, более корректно говорить, что с помощью шаблонизатора мы хотим отделить Бизнес-логику от Вью-логики или проще говоря логики вывода. Но здесь мы натыкаемся на противоречие с вторым посылом, а именно попыткой отделить программный код от HTML. Ибо логика она и в Африке логика и может быть реализована только с помощью алгоритмических языков, а никак не с помощью HTML, который как известно является языком разметки. Забегая вперед, скажу что конечно отделить чистый HTML невозможно, но можно постараться сделать шаблон максимально к нему приближенным.

Второй проблемой при разделении логик, является то, что прежде чем перейти к вью, все данные должны быть подготовлены в бизнес части приложения и внесены в отдельные структуры. Но, черт возьми, это же идет в разрез с тем принципом, благодаря которому PHP стал самой популярной технологией в вебе – внедрение извлечения данных непосредственно в нужное место HTML. Хрестоматийным случаем является перебор результатов выборки из базы данных:

while ($row = mysql_fetch_assoc($recordset))
{
    ?> <td><?= $row['field'] ?></td> <?php
}

Как только внутри while мы написали ?> или echo – все. Никакого разделения логик нет. Значит надо выгружать все в промежуточный массив $rows:

while ($row = mysql_fetch_assoc($recordset))
{
    $rows[] = $row;
}

, который будет использован во вью-части. Но позвольте, в ней будет тоже цикл, который что самое обидное будет перебирать ровно те же самые элементы второй раз. Ровно по такому принципу работает Smarty. В данном случае, его шаблон будет иметь вид:

{foreach key=key item=item  from=$rows}
        <td>{$item.field}</td>
{/foreach}

Его шаблоны – это инструкции на алгоритмическом макроязыке написанном на PHP. Своего рода PHP на PHP.

Осознав данный факт, многие программисты ударяются в другую крайность. Зачем, говорят они, нам нужен шаблонизатор, когда PHP — сам прекрасно выполняет данные функции. Действительно, переписав шаблон на чистый PHP, мы будем иметь не многим более сложную конструкцию:

foreach($rows as $key => $item)
{
    ?><td><?=  $item['field'] ?></td><?php
}

Отличием и преимуществом Smarty будет то, что данные могут быть переданы в шаблон непосредственно после сборки в промежуточный массив, причем в любое место шаблона, а в случае чистого PHP вся бизнес-логика должна быть завершена, прежде чем начнется непосредственно вывод. Да и вывод будет происходить строго сверху вниз, как и полагается алгоритмическому языку.

Также надо заметить, что Smarty предлагает ряд удобных для построения хорошего вью вещей – признаки первой и последней итерации, четная и нечетная итерации и.т.п. (кстати, почему подобных псевдопеременных нет в PHP?).

Но все эти бонусы по сравнению с необходимостью нудного изучения синтаксиса Smarty да и учитывая его тяжеловесность часто заставляет делать выбор в пользу чистого PHP. Данный подход получил название “нативных шаблонов”, и в различных статьях ратуют за него уже достаточно давно. Желающие могут ознакомится:

http://spectator.ru/technology/php/easy_templates 
http://larin.in/archives/16 

Как видите, идея живет и процветает в программистских умах. Автор первой статьи, которая получила широкую известность в рунете, предложил противникам данного подхода “побриться бритвой Оккама”. Побреемся, не заросшими же ходить :). Первый недостаток уже указан выше – по многим данным, как и в случае со Smarty, нам придется пробежаться циклом два раза, что согласитесь не есть гут. Но есть и второй. В данных статьях рассматриваются крайне простые примеры – вывод переменной в нужное место шаблона или простой цикл. Это как доказывать теоремы, рассматривая только предельные случаи. А при усложнении верстки, “нативные шаблоны” превращаются в достаточно навороченный код. Не верите? Давайте рассмотрим пример:

Допустим нам надо вывести список футбольных команд (кстати, Спартак – чемпион!) с городами приписки. В реальной жизни, скорее всего, данные будут извлекаться из базы данных, но для простоты отладки и запуска разместим их в массиве:

$teams = Array(
    Array('name' => 'Спартак', 'city' => 'Москва'),
    Array('name' => 'Зенит', 'city' => 'Санкт-Петербург'),
    Array('name' => 'Шинник', 'city' => 'Ярославль'),
    Array('name' => 'Сатурн', 'city' => 'Раменское'),
    Array('name' => 'Луч', 'city' => 'Владивосток')
);

А теперь предположим, что злой дизайнер-верстальщик запихнул их в таблицу, так чтоб город был под названием команды, да еще все это счастье в несколько столбцов. Т.е. требуемый HTML должен выглядеть следующим образом:

<table cellpadding=«3»>
<tr>
    <td><b>Спартак</b></td>
    <td><b>Зенит</b></td>
    <td><b>Шинник</b></td>
</tr>
<tr>
    <td>Москва<br><br></td>
    <td>Санкт-Петербург<br><br></td>
    <td>Ярославль<br><br></td>
</tr>
<tr>
    <td><b>Сатурн</b></td>
    <td><b>Луч</b></td>
</tr>
<tr>
    <td>Раменское<br><br></td>
    <td>Владивосток<br><br></td>
</tr>
</table>

И в браузере это выглядит как:

Спартак Зенит Шинник
Москва
 
Санкт-Петербург
 
Ярославль
 
Сатурн Луч
Раменское
 
Владивосток
 

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

<!-- BEGIN:index -->
<table cellpadding=«3»>
<!-- BEGIN:line -->
    <!-- BEGIN:names -->
    <tr>
        <!-- BEGIN:name -->
        <td><b>{team.name}</b></td>
        <!-- END:name -->
    </tr>
    <!-- END:names -->
    <!-- BEGIN:cities -->
    <tr>
        <!-- BEGIN:city -->
        <td>{team.city}<br><br></td>
        <!-- END:city -->
    </tr>
    <!-- END:cities -->
<!-- END:line -->
</table>
<!-- END:index -->

А контроллер:

$i = $j = 0;
foreach($teams as $value) // Здесь один цикл по всем командам
{
    $xtpl->assign('team', $value);
    $xtpl->parse('index.line.names.name');
    $xtpl->parse('index.line.cities.city');
    if (++$j == sizeof($teams) || ++$i == 3)
    {
        $xtpl->parse('index.line.names');
        $xtpl->parse('index.line.cities');
        $xtpl->parse('index.line');
        $i = 0;
    }
}
$xtpl->parse('index');
$xtpl->out('index');

Попробуем теперь сделать нативный шаблон. Честное слово, я старался писать как можно компактнее, но тем не менее у меня получилось вот что:

<table cellpadding=«3»>
<?php
for($i=0; $i<sizeof($teams)/3; $i++)
{
?>
<tr>
<?php
    for($j=0; $j<3; $j++)
    {
        $index = $i*3 + $j;
        if ($index >= sizeof($teams)) break;
        ?>
        <td><b><?php echo $teams[$index]['name'] ?></b></td>
        <?php
    }
?>
</tr>
<tr>
<?php
    for($j=0; $j<3; $j++)
    {
        $index = $i*3 + $j;
        if ($index >= sizeof($teams)) break;
        ?>
        <td><?php echo $teams[$index]['city'] ?><br><br></td>
        <?php
    }
?>
</tr>
<?php
}
?>
</table>

Заметьте, вместо одного очевидного цикла, перебирающего команды, мы имеем три (не забываем, что в реальной жизни есть еще и четвертый, который готовит данные). В добавок ко всему, циклы вложенные. Да и перебирают они не просто команды, а некоторые абстрактные сегменты и смещения. Как говорится, почувствуйте разницу! Конечно, данный вьювер можно попробовать переписать, чтобы цикл перебирал все же команды (желающие могут попробовать), но что-то подсказывает мне, что код будет еще хуже читаемый, и уж точно не удастся избежать конструкций типа:

if (первый в строке)
{
    echo '<tr>';
}
else if (последний в строке)
{
    echo '</tr><tr>';
}

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

И наконец, представьте, что данные команды фигурируют в дизайне еще где-то. Например, где-то в шапке сайта должны быть отображены все их эмблемы. Что будет в случае нативного шаблона? Правильно еще один цикл все опять по тому же самому. В случае блочного шаблонизатора? Всего лишь еще один вызов parse. Один!

Теперь рассмотрим сам шаблон. Как и полагается, он достаточно прост и читаем. XML – образен, если можно так выразится. Блоки имеют осмысленные названия. ‘line’ – это реально линия команд, как она выглядит в дизайне т.е. “название – подпись” в ряд. Надеюсь, смысл остальных блоков тем более понятен. Здесь возникает, как правило, организационный вопрос: “Кто должен писать данный шаблон программист или верстальщик”. Мое мнение – программист. Но, не смотря на это, использование такого подхода перекладывает кучу работы по доводке и вылизыванию дизайна на верстальщика. После того, как шаблоны сформированы можно дать доступ к директории с ними. Как показывает практика, крайне простой синтаксис верстальщик осваивает быстро и дальше работает с шаблонами самостоятельно. 90% задач по переоформлению он решит самостоятельно. Главное донести до него одну мысль – содержимое блоков способно тиражироваться.

Итак, теперь резюмируем полученные преимущества:

  1. Шаблон позволяет генерить HTML не в порядке исполнения PHP скрипта т.е. сверху вниз, а в порядке получения данных и непосредственно в процессе их получения.
  2. Шаблон имеет хорошо читаемый и правильно структурированный (XML-образный) вид. Блоки имеют осмысленные названия. Даже если мы удалим все конструкции XTemplate, то получим нормальный HTML, так сказать предельный случай как будто бы у нас один экземпляр данных.
  3. Шаблон упрощает PHP код, избавляя его от конструкций налагаемых дизайном.

Источник: http://pyha.ru

 

Ratings:

No comments yet