Вниманию читателей предлагается простой в использовании обработчик шаблонов на языке PHP с поддержкой условных конструкций, циклов, подключения вложенных шаблонов и запуска сторонних модулей (функций). Время обработки шаблона составляет в среднем 20-70 мс. Для работы требуется подключение одного php-файла размером 20 Кб (600 строк кода). Код стабилен и применяется на нескольких работающих проектах.
Перейдём сразу к практике и посмотрим, как выглядит шаблон, взяв для примера немного упрощенную версию HTML-кода сайта webew.ru:
<head>
/* Многострочные комментарии - как в C или PHP */
<title>{*title*}</title>
<base href="{*=BASEHREF*}"> /* константа */
/* условные конструкции: */
{?*keywords*}
<meta name="keywords" content="{*keywords*}">
{*keywords*?}
{?*description*}
<meta name="description" content="{*description*}">
{*description*?}
</head>
<body>
<div id="logo">
<a href=""><img src="{*logo.image*}" alt="{*logo.alt*}"></a>
</div>
<div id="menu">
/* цикл: */
{%*menu*}
<a href="{*menu:url*}">{*menu:title*}</a>
{*menu*%}
</div>
<div id="content">
{*content*}
</div>
<div id="footer">
webew.ru © {*year*}
</div>
</body>
</html>
А вот соответствующий код PHP:
ini_set('pcre.backtrack_limit', 1024*1024); // (см. ниже)
$DATA['title'] = 'Webew: теория и практика веб-технологий ';
define('BASEHREF', 'http://webew.ru/');
$DATA['logo'] = array(
'image' => 'i/logo.gif',
'alt' => 'Логотип webew.ru'
);
$DATA['menu'] = array(
array('url' => 'css', 'title' => 'CSS'),
array('url' => 'php', 'title' => 'PHP'),
array('url' => 'seo', 'title' => 'Интернет-маркетинг'),
array('url' => 'c', 'title' => 'C/C++')
);
$DATA['content'] = 'Приветствуем вас на webew.ru!';
require_once 'websun.php'; // подключаем файл с шаблонизатором
$tpl = 'templates/main.tpl'; // путь к шаблону
$html = websun_parse_template_path($DATA, $tpl); // запуск шаблонизатора
echo $html; // получили обработанный шаблон, отдаем клиенту результат
Несмотря на то, что переменные $DATA['keywords'] и $DATA['description'] не установлены, при обработке шаблона не возникнет никаких ошибок или предупреждений.
Условная конструкция вида {?*keywords*} что-то {*keywords*?} означает "вставить что-то, если в переменной, переданной шаблону (в данном случае $DATA) присутствует элемент keywords и при этом он не является пустой строкой, нулём, FALSE, NULL или пустым массивом". Соответственно, конструкция {?!*keywords*} ... {*keywords*?!} срабатывает, если какое-нибудь из этих условий не выполняется.
Шаблонизатор не генерирует ошибок или предупреждений в том случае, если какой-либо из запрошенных переменных или констант вообще не существует (вместо них молчаливо будет вставлена пустая строка) поэтому формат передаваемых данных может быть нестрогим.
В цикле ключи массива указываются через двоеточие — {*menu:title*}, а вне цикла через точку — {*logo.image*}.
Можно также обращаться к переменным глобальной области видимости, в т.ч. суперглобальным массивам. Для этого перед именем переменной нужно поставить знак доллара. Например, подстановка в шаблон переменной $_GET['foo'] выглядит так: {*$_GET.foo*}.
Вложенные шаблоны
Обычно код бывает удобно разбить на несколько шаблонов, которые затем подключать по мере необходимости. Пусть шаблон меню находится в файле menu.tpl. Тогда код из главного шаблона
{%*menu*}
<a href="{*menu:url*}">{*menu:title*}</a>
{*menu*%}
</div>
в неизменном виде переместится в menu.tpl, а вместо него будет
<div id="logo">
...
</div>
{* +menu.tpl *} /* подключаем шаблон меню */
<div id="content">
...
Путь к шаблону может также передаваться в переменной. Например, если записать путь к шаблону меню в $DATA['menu_template'], то подключение его примет вид
Существует несколько способов указания путей к шаблонам.
Самый наглядный метод — это указание абсолютного пути к файлу шаблона в файловой системе — что-то наподобие
$tpl = '/home/webew.ru/htdocs/templates/main.tpl'
Аналогично, для подключения шаблона меню с помощью абсолютного пути приходится писать
{* +/home/webew.ru/htdocs/templates/menu.tpl *}
Если в начале пути к шаблону отсутствует слэш, то путь интерпретируется относительно месторасположения текущего шаблона (это особенно удобно для группы связанных шаблонов, которые становится легче читать, и к тому же можно все вместе переносить, не меняя при этом путей в них, если они записаны как относительные).
Обычно все шаблоны находятся в каком-то одном каталоге (или его дочерних) и шаблонизатору можно указать так называемый корневой каталог шаблонов — такой, относительно которого он будет интерпретировать пути к ним. Корневой каталог передается третьим аргументом:
websun_parse_template_path(
$DATA,
$tpl,
'/home/webew.ru/htdocs/templates'
);
(если корневой каталог при вызове функции не указан, за него принимается каталог, в котором находится файл шаблонизатора).
Сообщать шаблонизатору о том, что интерпретировать путь нужно относительно корневого каталога шаблонов, следует, поместив в начало пути символ ^:
$tpl = '^/main.tpl';
Можно указывать также путь к шаблону относительно корневого каталога веб-сервера (используется переменная $_SERVER[DOCUMENT_ROOT]). Для этого в начало пути нужно поместить знак доллара.
Таким образом, путь к одному и тому же шаблону (как при его подключении его из других шаблонов, так и при вызове websun_parse_template_path) можно указать четырьмя разными способами:
// корневой каталог шаблонизатора - /home/webew.ru/htdocs/templates
// тогда следующие четыре записи эквивалентны:
$tpl = 'menu.tpl'; // путь относительно текущего шаблона
$tpl = '^/menu.tpl'; // относительно корневого каталога шаблонизатора
$tpl = '$/templates/menu.tpl'; // относительно корневого каталога веб-сервера
$tpl = '/home/webew.ru/htdocs/templates/main.tpl'; // абсолютный путь для ФС
При запуске шаблонизатора (вызов функции websun_parse_template_path) и при подключении не связанных друг с другом шаблонов рекомендуется использовать путь относительно корневого каталога шаблонизатора, в остальных случаях как правило удобнее пользоваться относительными путями.
Передача во вложенный шаблон части массива данных
Зачастую подключаемый шаблон имеет дело не со всем массивом данных, а только с какой-то его частью (например, в шаблоне меню не нужны никакие переменные, кроме $DATA[menu]). Записывать каждый раз префикс массива в таком шаблоне становится излишним и неудобным, код шаблона загромождается. Чтобы этого избежать, подключаемому шаблону можно передать не весь массив, а лишь нужный элемент.
Вот как будут выглядеть основной шаблон и шаблон меню при таком подходе:
(основной шаблон)
{* + *menu* | menu.tpl *}
/* путь к шаблону в таком случае указываем через вертикальную черту */
...
(или другой вариант — с передачей пути в переменной:)
Шаблон меню:
{%**}
<a href="{*:url*}">{*:title*}</a>
{**%}
</div>
Конструкции вида {**}, {%**} и {?**} означают обращение к корневой переменной, переданной в шаблон. А запись вида {* + шаблон *} есть укороченная форма конструкции {* + ** | шаблон *} (** — в подключаемый шаблон передается вся корневая переменная).
Можно пойти еще дальше и вынести код для элемента меню в отдельный шаблон:
{%**} {* + *:* | menu-item.tpl *} {**%}
</div>
(здесь конструкция *:* указывает при итерации на каждый из элементов корневого массива и означает передачу их в подключаемый шаблон)
menu-item.tpl:
Вызов сторонних модулей
Есть возможность вызывать специально подготовленные функции-модули прямо из шаблона. Например, вместо $DATA['year'] = date('Y') и
можно записать
а в PHP:
Как видно из примера, имя вызываемой таким образом функции должно содержать префикс module_ (подобное ограничение пространства имен введено из соображений безопасности — чтобы из шаблона нельзя было вызвать какую угодно функцию, а набор их явно и сознательно контролировался разработчиками). Если функции с таким названием нет, будет сгенерирована ошибка уровня E_WARNING.
Результат работы модуля можно не только вернуть напрямую в виде строки (как в примере выше), но и передать для подстановки в какой-нибудь шаблон, где этот результат будет принят в качестве корневой переменной. Например,
Имя функции можно также передавать шаблону в переменной. Например, если $DATA['menu_module'] = 'menu' и $DATA['menu_template'] = 'menu.tpl', то вызов будет выглядеть так:
Если префикс module_ уже присутствует в переменной, шаблонизатор не будет повторно его подставлять (т.е. можно записать как $DATA['menu_module'] = 'menu', так и $DATA['menu_module'] = 'module_menu').
Вызываемым таким образом функциям можно передавать аргументы, которыми могут выступать как скалярные величины, так и переменные шаблона. В примере ниже функции module_menu передаются три аргумента — переменные $_SERVER[REQUEST_URI] и $DATA[title], а также строка '1':
Переменные следует записывать без пробелов вокруг звездочек. Аргументы, не являющиеся переменными, интерпретируются как строки, окружающие пробелы в этом случае включаются в их состав.
Следует отметить, что вызов модулей приводит к делокализации логики приложения (т.к. она частично переносится в шаблоны), понижая, таким образом, его управляемость, поэтому прибегать к нему следует лишь в случае, когда обратное крайне неудобно.
Скачать
Чтобы начать использовать шаблонизатор, достаточно скачать и подключить его php-файл.
Еще кое-что
Ниже приводится некоторая дополнительная информация, которая может быть полезной при работе с шаблонизатором.
Помимо websun_parse_template_path, принимающей путь к шаблону, есть еще функция websun_parse_template, которой вместо пути шаблона передается его содержимое:
$template = '
{*keywords*}
...
';
$html = parse_websun_template(
$DATA,
$template,
'/home/webew.ru/htdocs/templates' // можно не указывать
);
(на самом деле обработчик шаблонов реализован в виде объекта, создание и настройка которого для удобства инкапсулированы в функции websun_parse_template и websun_parse_template_path; если интересно, как это происходит, можно посмотреть код этих функций)
Условия можно конструировать с использованием сравнения:
{?!*a=1*} a не равно 1 {*a=1*?!}
{?*a>1*} a больше 1 {*a>1*?}
{?!*a>1*} a не больше (т.е. меньше или равно) 1 {*a>1*?!}
{?*a=b*} а равно b {*a=b*?}
{?*a="раз"*} a - это "раз" {*a="раз"*?}
Часть после знака сравнения интерпретируется как имя переменной, если только она не заключена в двойные кавычки (в этом случае для сравнения берется строка внутри кавычек) или же не состоит полностью из цифр (тогда берется соответствующее число).
Для ясности следует отметить, что условные конструкции можно использовать и внутри циклов. Например, можно выделить активный пункт меню, поместив его в <strong> вместо обычных ссылок:
array('url' => 'css', 'title' => 'CSS'),
array('url' => 'php', 'title' => 'PHP', 'active' => TRUE), // активный пункт меню
array('url' => 'seo', 'title' => 'Интернет-маркетинг'),
array('url' => 'c', 'title' => 'C/C++')
);
Шаблон:
{?!*menu:active*}<a href="{*menu:url*}">{*menu:title*}</a>{*menu:active*?!}
{?*menu:active*}<strong>{*menu:title*}</strong>{*menu:active*?}
{*menu*%}
Следует отметить, что, несмотря на то, что для конкретной записи элемент 'active' может быть вообще не установлен, при работе шаблонизатора PHP не будет генерировать предупреждений или замечаний (т.е. засорять журнал ошибок), поскольку в шаблонизаторе для таких случаев есть в т.ч. проверка с помощью isset().
Иногда сократить запись можно с помощью конструкции вида {*var1|var2|var3|"раз"*}, которая заменяется на первую из перечисленных непустую альтернативу (в данном примере, если все переменные окажутся пустыми, будет подставлена строка "раз").
При использовании PHP 5.2.2 и выше следует обратить внимание на настройку pcre.backtrack_limit, которая ограничивает максимальный размер строки, обрабатываемой функциями семейства preg (по умолчанию это 100 Кб). Характерный признак недостаточно большой величины pcre.backtrack_limit — получение пустой строки в результате обработки шаблона в случае, когда этого быть не должно. Избежать этого можно, увеличив это значение (лучше с большим запасом — к растрате ресурсов это не приведет):
Итерация по одномерному массиву (вида $DATA['list'] = array(10,20,30,40,50)) делается очень просто. Например, вывести его элементы по одному в строке можно так:
{%*list*} {*list:*}<br> {*list*%}А вообще можно использовать массивы любой размерности (не только двумерные), а циклы строить — любой вложенности:
array(
'id' => 1,
'title' => 'Москва',
'codes' => array(77, 97, 99, 177, 197),
'neighbours' =>
array('id' => 25, 'title' => 'Калуга'),
array('id' => 32, 'title' => 'Тула'),
array('id' => 47, 'title' => 'Владимир'),
array('id' => 59, 'title' => 'Рязань'),
array('id' => 134, 'title' => 'Тверь'),
array('id' => 15, 'title' => 'Смоленск'),
array('id' => 37, 'title' => 'Ярославль')
),
),
array(
'id' => 2,
'title' => 'Санкт-Петербург',
'codes' => array(78, 98),
'neighbours' =>
array('id' => 69, 'title' => 'Петрозаводск'),
array('id' => 78, 'title' => 'Вологда'),
array('id' => 10, 'title' => 'Новгород'),
array('id' => 117, 'title' => 'Псков')
),
...
);
Шаблон:
<h1>{*cities:title*}</h1>
<p>
Автомобильные коды:
{%*cities:codes*}
{*cities:codes:*} /* список кодов - одномерный массив */
{*cities:codes*%}
</p>
<p>
Соседние регионы:
<br>
{%*cities:neighbours*}
<a href="/city/{*cities:neighbours:id*}">
{*cities:neighbours:title*}
</a>
<br>
{*cities:neighbours*%}
</p>
{*cities*%}
К элементам такого массива можно обращаться и вне цикла, например, {*cities.0.neighbours.1.title*} — 'Тула' и т.п.
Внутри цикла есть также возможность обратиться к ключу массива - с помощью инструкции ^KEY, например:
{%*cities*}№ {*cities:^KEY*}. {*cities:title*}<br>
{*cities*%}
А с помощью ^COUNT можно оперировать числом элементов в массиве:
Всего городов: {*cities^COUNT*}.Подключение шаблонов внутри циклов можно переписать покороче:
{%*menu*} {* +*menu:* | menu-item.tpl *} {*menu*%}
/* но лучше - так (перед именем передаваемой в шаблон переменной появился знак %) */
{* +*%menu* | menu-item.tpl *}
Аналогичное сокращение действует и для подключения шаблона внутри условия:
{?*menu*} {* +*menu* | menu-item.tpl *} {*menu*?}
/* но лучше - так (перед именем переменной появился знак вопроса) */
{* +*?menu* | menu-item.tpl *}
Есть возможность передавать в переменной не путь к шаблону, а непосредственно его содержимое (иногда это бывает удобно). Пользоваться этим можно так же, как и обычными вложенными подшаблонами:
/* фактически дополняем переменной
текущий шаблон; так можно, грубо говоря,
сделать её содержимое динамическим
с помощью шаблонизатора */
{* + *subvar* | >*tpl* *}
/* можно и часть массива */
{* + *?subvar* | >*tpl* *}
/* если в subvar что-то есть.. */
{* + *%subvar* | >*tpl* *}
/* пройтись по переменной в цикле */
{* + *@module* | >*tpl* *}
/* дать переменную модулю как шаблон */
© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.

