О наболевшем, или не Smarty единым
Последнее время мне приходилось проводить собеседования с претендентами на должность php-программистов. Беседуя с кандидатами, заметил, что когда речь заходит о шаблонизаторах (термин, ИМХО, несколько коряв, но поскольку прочно прижился в веб-сообществах позвольте его использовать) все неизменно называют одно единственное слово – Smarty. Причем мнения о Smarty бывают диаметрально противоположные от: “Знаю, активно использую”, до: “Ненавижу эту гадость”. Аналогичная картина наблюдается на, посещаемых мною, интернет-форумах. В умах подавляющего большинства разработчиков слова Smarty и шаблонизатор – синонимы. Надо ли говорить, что на самом деле это не так? В добавок к сложившемуся в головах стереотипу “шаблонизатор это Smarty”, у меня еще создалось впечатление, что многие начинающие разработчики вообще не понимают зачем именно им, именно в конкретном проекте нужен шаблонный парсер и их выбор архитектуры базируется на одной из двух, витающих по интернету, мантр: “настоящий профессионал всегда пользуется шаблонизатором для отделения логики от представления” или “php сам по себе шаблонизатор и незачем изобретать велосипед”. На самом деле и то и другое верно только отчасти и не в коем случае, не следует данные мантры принимать как истину в последней инстанции. А вот как точку, отсчета мы их можем принять и постараться рассмотреть по возможности отстраненно. Данный цикл статей преследует следующие цели:
- Постараться разобраться во всех тех неоднозначностях и непониманиях, что царят вокруг шаблонизаторов. В т.ч. развенчать “догмы” и “стереотипы”. :)
- Предложить конкретные решения и подходы и сделать введение в два продукта – XTemplate и Blitz, про которые практически нет никакой информации, кроме официальной документации (увы, практика показала, что начинающий программист уже на этапе продирания сквозь документацию отказывается от идеи использовать данный продукт, из-за банального непонимания).
XTemplate – шаблонный движок для “маленьких”
Прежде чем, пускаться в теоретические дебри и рассуждения, рассмотрим готовый продукт – шаблонизатор XTemplate, который можно прямо сейчас скачать (http://www.phpxtemplate.org/XTemplateDownloads) и прямо сейчас использовать. Это одно из крайне простых решений, которое, тем не менее, содержит все необходимое для создания более-менее стройной архитектуры средне-статистического веб–сайта. Основные его приемущества заключаются в следующем:
- Маленький размер. Для работы требуется всего один небольшой php-файл.
- Крайне простой синтаксис шаблонов, на изучение которого уйдут считанные минуты.
- Написан на PHP и не использует никаких дополнительных расширений, т.е. вы 100% сможете его использовать везде, где будет работать или отлаживаться ваш проект. (Лично меня всегда удивляло, что для огромного количество разработчиков именно этот пункт является решающим фактором, но факт есть факт).
- Имеет “блочную идеологию” (В чем именно ее преимущество мы подробнее рассмотрим позднее).
Для работы с 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 код. Про блоки надо понять три вещи:
- Содержимое блока никаким образом не будет отображено, пока в контролере явно не будет указано это сделать. Данная операция называется парсинг блока и выполняется командой $xtpl->parse(‘имя блока’); (В принципе, есть возможность задать вывод по умолчанию для не пропарсенных блоков с помощью функции set_null_block)
- Содержимое блока тиражируется, столько раз, сколько выполнена команда parse. Выглядит это следующим образом — представьте, что при первом вызове parse содержимое блока отображается в шаблоне, а каждом последующем вы как будто выполняете операции его выделения – копирования – вставки, снизу от предыдущего отображения.
- Блоки могут вкладываться друг в друга, при этом парсинг осуществляется по принципу от внутренних блоков к внешним. т.е. шаблон разворачивается как бы изнутри.
Пришло время, проиллюстрировать вышесказанное примером. Итак, пусть шаблон имеет вид:
Шаблон:
<!-- 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”. А вот теперь стоп! Давайте разбираться и с тем и другим. Сначала разбираемся с логикой. Проблема в том, что логику нашего приложения можно разделить в свою очередь на две части:
- Бизнес-логика т.е. извлечение данных и БД, их специфическая обработка, математически расчеты и.т.п.
- Вью-логика т.е. собственно оборачивание в 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% задач по переоформлению он решит самостоятельно. Главное донести до него одну мысль – содержимое блоков способно тиражироваться.
Итак, теперь резюмируем полученные преимущества:
- Шаблон позволяет генерить HTML не в порядке исполнения PHP скрипта т.е. сверху вниз, а в порядке получения данных и непосредственно в процессе их получения.
- Шаблон имеет хорошо читаемый и правильно структурированный (XML-образный) вид. Блоки имеют осмысленные названия. Даже если мы удалим все конструкции XTemplate, то получим нормальный HTML, так сказать предельный случай как будто бы у нас один экземпляр данных.
- Шаблон упрощает PHP код, избавляя его от конструкций налагаемых дизайном.
Источник: http://pyha.ru
Comments: (0)