Основным отличием систем, основанных на подстановке данных, является то, что они позволяют, используя определенный синтаксис, определять места вставки данных в HTML темплейты. По сути все имеющиеся системы работы с темплейтами основаны именно на этом принципе и единственное, что их различает - синтаксис, используемый для задания темплейтов и набор возможностей, предоставляемый системой.
Приведу одну из самых простых систем, основанных на этом принципе. Это всего лишь одна небольшая функция (чуть больше 40 строчек кода) и была написана мной буквально за пару часов, но, тем не менее, позволяет практически все, что и большинство систем "средного уровня", имеющиеся в интернете. Исходные тексты примеров вы можете взять в виде ZIP архива, а здесь я приведу их в комментариями.
Но сначала краткое описание синтаксиса для описания темплейтов, применяемое в данной системе. Я приведу его в EBNF-подобной системе и затем дам необходимые пояснения.
"Ключ" для подстановки:
<Key> ::= '{'<Key name>[' '<Default value>]'}'
Ключем для подстановки здесь называется часть текста темплейта, которая будет впоследствии заменена на некоторые данные, переданные функции - обработчику темплейтов. Он состоит из двух основных частей: имени (уникального в пределах данного темплейта) и необязательного значения по-умолчанию. Оно будет использоваться в случае, если при обработке темплейта для него не было задано значения. В случае, если значение по-умолчанию также не было задано - этот ключ будет заменен на пустую строку.
Значение по-умолчанию может также быть использовано для задания специальной обработки. Ниже приведены 3 различных типа синтаксиса, допустимые для значения по-умолчанию:
Как видите, тип обработки для значения по-умолчанию указывается в первом символе.
Если это символ '#', то все значение рассматривается как "вставить результат обработки темплейта с именем <Template name> с заданными параметрами в качестве значения для этого ключа подстановки". Т.е. обработчик темплейтов будет вызван рекурсивно для обработки тепмлейта с заданным именем и заданным списком данных для подстановки, а результаты обработки этого темплейты будут использованы в качестве значения для подстановки.
Если это символ '!', то процесс обработки похож на предыдущий, с той лишь разницей, что вместо вызова обработчика темплейтов производится вызов пользовательской функции с заданным именем и ей в качестве параметра передается массив данных, заданных в этом ключе (структура массива такая же, как и для самой функции обработки темплейтов). Результаты работы функции будут использованы в качестве значения для подстановки.
Символы, имеющие специальное значение могут быть вставлены в текст, используя их escaping sequences:
Escaping символов, имеющих специальное значение
Вне ключей для подстановки
{
{l}
}
{r}
Внутри ключей для подстановки
{
{{
}
}}
Ниже приведен текст функции, которая непосредственно занимается обработкой темплейтов, используя описанный выше синтаксис. Текст достаточно поднобно откомментирован.
Файл templates.function.php
<?php
// Вставка в страницу HTML кода на основе темплейтов
// Параметры:
// $template - темплейт с HTML кодом, который будет использоваться как основа
// $params - массив с данными, которые будут использоваться для подстановки.
function insertTemplate($template,$params=array())
{
// Убираем из текста темплейта все escaped символы (они будут заменены
// на необходимые значения позже) Это необходимо, чтобы облегчить задачу
// разбиения темплейтов с помощью регулярных выражений
$template = strtr($template,array('{{'=>"\x03",'}}'=>"\x04"));
// Используем регулярное выражение чтобы получить массив всех мест внутри темплейта,
// которые должны быть заменены на результаты подстановки.
preg_match_all("/\{([^\}]+)\}/i",$template,$matches);
// Если не было найдено ни одного места для подстановки -
// просто возвращаем исходный текст темплейта.
if (sizeof($matches[0])==0)
return($template);
// В этот массив мы будем собирать тексты, которые будут исползованы для
// подстановок в темплейт.
$replaces = array();
// Нам необходимо преобразовать все найденные места для подстановок внутри темплейта
// в регулярные выражения для их поиска. Тогда мы сможем впоследствии выполнить
// все подстановки одновременно, используя замену по массиву регулярных выражений.
for ($i=0;$i<sizeof($matches[0]);$i++)
$matches[0][$i] = '/'.preg_quote($matches[0][$i],'/').'/';
// Теперь нам необходимо подготовить тексты для замены
// Для этого нам необходимо обработать содержимое каждого из найденных
// мест для подстановок внутри темплейта.
for ($i=0;$i<sizeof($matches[1]);$i++)
{
// Преобразуем все escaped символы в нормальные. Символ разделения ' ' при этом
// заменяем на символ с кодом 0x01, чтобы не перепутать.
$match = strtr(strtr($matches[1][$i],array(' '=>"\x02",' '=>"\x01")),"\x02",' ');
// Проверяем, что из себя представляет строка, которую мы пытаемся обработать
if (strpos($match,"\x01")!==false)
// Эта строка содержит в себе несколько частей. Это значит, что кроме имени эта
// строка содержит какие-то параметры, которые требуют дополнительной обработки.
{
// Поскольку основная синтаксическая структура у нас состоит из 2 частей - имени
// и значения по-умолчанию - получаем эти две основные части в виде отдельных переменных
list($key,$default) = explode("\x01",$match,2);
// Исправляем regular expression для дальнейшей замены
$matches[0][$i] = "/\{$key\ [^\}]+\}/";
// Проверяем, чем является параметр, переданный внутри темплейта. Если он начинается
// с одного из специальных символов, то необходима дополнительная обработка этого значения.
// Однако это необходимо делать толлько в случае, если в переданных в функцию данных для
// замены нет текста для этой подстановки (потому что данные, переданные в качестве
// аргумента имеют более высокий приоритет).
if ((in_array($default[0],array('#','!'))) && (!isset($params[$key])))
{
// Получаем список аргументов. Первый символ отбраcываем, потому что это признак
// спеуиальной обработки и не относится к имени.
$words = explode("\x01",substr($default,1));
// Поскольку первым в полученном списке стоит имя, которое будет использоваться
// обработчиком - берем его в отдельную переменную и убираем из массива аргументов.
// Теперь в массиве $words - только список аргументов.
$name = array_shift($words);
// Проверяем, если количество аргументов - нечетное (т.е. нам необходим еще один, поскольку
// все аргументы рассматриваются как пары "имя-значение"), то добавляем пустую строку.
if ((sizeof($words)%2)!=0)
$words[] = '';
// Формируем массив параметров. Он должен быть в том же виде, в котором он передается
// в данную функцию (т.е. имя параметра задается в виде ключа ассоциативного массива).
$params = array();
for ($j=0;$j<sizeof($words);$j+=2)
$params[$words[$j]] = $words[$j+1];
if ($default[0]=='#')
// Символ '#' указывает на необходимость вставки темплейта с заданным именем
$default = insertTemplate($GLOBALS[$name],$params);
elseif ($default[0]=='!')
// Символ '#' указывает на необходимость вставки результатов работы пользовательской
// функции с заданным именем
$default = call_user_func($name,$params);
};
// Если в списке текстов для подстановки, переданных в качестве параметра в эту функцию,
// есть текст для подстановки с таким же именем, то используем его, потому что параметры,
// переданные в качестве аргумента имеют более высокий приоритет. Если же такого текста
// нет, то используем текст, имеющийся у нас в качестве значения.
$replaces[] = (isset($params[$key]))?$params[$key]:$default;
}
elseif ($match=='l')
// Эта строка - escaping для левой фигурной скобки, имеющей специальное значение.
$replaces[] = '{';
elseif ($match=='r')
// То же самое для правой фигурной скобки
$replaces[] = '}';
else
// Эта строка имеет только имя. Если в списке текстов для подстановки, переданных
// в качестве параметра в эту функцию, есть текст для подстановки с таким именем,
// то используем его, в противном случае используем в качестве замены пустую строку.
$replaces[] = (isset($params[$match]))?$params[$match]:"";
};
// Теперь у нас есть все необходимые данные и мы можем выполнить замену. Поскольку все
// строки, которые необходимо заменить в данном темплейте сконвертированы в регулярные
// выражения - необходимо просто выполнить замену по имеющимся массивам. Кроме того
// здесь же мы возвращаем нормальные значения escaped символам, которые мы убирали в начале.
return(strtr(preg_replace($matches[0],$replaces,$template),array("\x03"=>'{',"\x04"=>'}')));
};
?>
Теперь посмотрим, как можно сгенерировать ту же самую простейшую страничку, используя приведенную выше функцию.
Файл templates.php содежит описание всех необходимых темплейтов. Очень похоже на предыдущий вариант этого файла, но здесь в темплейтах используется описанный выше синтаксис для вставки текста.
// Функция генерации меню сайта. Она вызывается парсером темплейтов
// во время обработки темплейта $tplMenu.
function createMenu()
{
global $menu;
$html = '';
// Вся генерация содержимого меню сводится все к тому же вызову парсера темплейтов.
// При этом в качестве аргументов передаются данные для каждого из имеющихся пунктов меню.
foreach($menu as $item)
$html .= insertTemplate($GLOBALS['tplMenuItem'],array('url'=>$item[0],'name'=>$item[1]));
return($html);
};
// Функция генерации содержимого страницы. В нашем случае она просто возвращает переменную.
function createPageContent()
{
return($GLOBALS['content']);
};
// Как видите, после всех подготовительных шагов весь код программы сводится к одной строчке
// Мы просто вызываем парсер темплейтов для обработки основного темплейта страницы, а все
// необходимые связи между темплейтами у нас прописаны непосредственно внутри них, что позволит
// впоследствии легко изменить их не меняя кода. Что, собственно, нам и требовалось.
echo insertTemplate($tplPage,array('title'=>$title));
?>
Как видите - код становится намного более компактным и логичным с применением темплейтов. И, кроме того, даже такая простейшая система обработки темплейтов значительно упрощает вам работу. Вы получаете возможность контролировать отдельно логику программы и отдельно - ее визуальную часть, чего мы, собственно, и добивались.
Кстати, эту функцию можно применять не только для генерации HTML (все же она слишком проста для этого), а и для других целей. Например таких, как генерация e-mail. Ведь иногда бывает необходимо сгенерировать текст письма по шаблону, добавив в него какую-то информацию. Использование этой простой функции поможет вам решить эту задачу быстро и легко.
Системы работы с темплейтами
Как я уже говорил - приведенная мной в предыдущем разделе функция для обработки темплейтов слишком проста, чтобы претендовать на роль реальной системы, пригодной для практического использования. Но в интернете вы можете обнаружить множество подобных систем разной степени "навороченности". Я приведу лишь несколько:
Исторически одной из самых первых подобных систем была FastTemplate. Она написана еще для PHP3 и на данный момент, похоже, уже не поддерживается. Все остальные варьируются по сложности и мощи поддерживаемого ими синтаксиса внутри темлейтов, а также наличием дополнительных сервисов.
Самой мощной системой на данный момент похоже является Smarty. Кроме достаточно мощного и гибкого языка (а столь развитый синтаксис иначе как языком назвать по-моему просто нельзя) она имеет и еще рад особенностей, выделяющих ее из всего ряда систем обработки темплейтов, имеющихся на данный момент. Самой замечательной ее особенностью является возможность "компиляции" темплейтов непосредственно в PHP скрипты! Т.е. однажды выполнив парсинг темплейта Smarty генерирует PHP скрипт, который в дальнейшем выполняет ту же работу значительно быстрее.
Поскольку сам я не пользуюсь ни одной из систем обработки темплейтов, предпочитая XML-технологии, я предоставляю вам возможность самим попробовать и сравнить различные системы и выбрать ту, которая наиболее подойдет именно вам.
Напомню, что вы можете скачать исходные тексты всех примеров, приведенных в этой статье в виде ZIP архива.
Заключение
В этом выпуске мы рассмотрели один из наиболее важных механизмов, необходимых при создании любого более-менее большого проекта. Однако, несмотря на все свои достоинства механизмы темплейтов имеют и свои недостатки. Один из основных - отсутствие какой-либо стандартизации синтаксиса между различными системами обработки темплейтов. Каждый автор как првило придумывает свой собственный "самый лучший и удобный" синтаксис и в результате эти системы живут каждая сама по себе.
В дальнейшем мы рассмотрим альтернативу системам темплейтов - технологии XML и XSLT. Эти технологии являются стандартами W3C и, следовательно имеют серьезную поддержку, огромное количество документации и примеров, большое количество программ для работы с данными в этих форматах, их создания, проверки и т.п. PHP тоже имеет расширения для работы с этими технологиями и в будующих выпусках мы рассмотрим, как можно использовать эти технологии для генерации динамических web-страниц.