Шаблонизатор на PHP своими руками
Автор: elrevin
Эта статья предназначена для тех кто не относится скептически к написанию того что уже написано, в общем она для энтузиастов, каким я сам и являюсь.
Начнем с того, что определим что будет уметь наш шаблонизатор:
- выделение из шаблона на блоков
- обработка блоков
- подстановка переменных
Блоки.
Блоки могут быть "циклические" и "не циклические". К первым отнесем блоки приразборе которых шаблонизатор выполнит цикл с определенными параметрами и вставит "внутренности" блока в результирующую страницу несколько раз. Они нам понадобятся массовой для публикации данных (например список заголовков новостей). Ко вторым отнесем условные конструкции (зачастую без них не обойтись).
Имена блоков будем хранить в массиве:
$Blocks=array (array('открывающий тег', 'закрывающий тег',признак цикличного блока));
Чтобы было понятнее разберем пример.
есть таблица новостей:
create table news ( id int(10) unsigned NOT NULL auto_increment, title varchar(128), body text, ndate date, primary key (id) );
нужно создать шаблон для вывода этих новостей и php скрипт обрабатывающий этот шаблон.
Договоримся - теги блоков и переменных будем заключать в HTML комментарии:
<!--news--> содержимое блока <!--end_news-->
теперь функция выделения блока или переменной:
function ParseTmpl($Block) { global $Blocks; global $Vars; // Ищем блоки $Pos=0; while (($Pos=strpos($Block,'<!--',$Pos))!==false) { $BlockId=GetIsBlock($Block,$Pos); if ($BlockId!=-1) { // ищем закрывающий тег блока $BlockEnd='<!--'.($Blocks[$BlockId][1]).'-->'; if ($EndIndex=strpos($Block,$BlockEnd,$Pos)) { $EndIndex+=strlen($BlockEnd); $SubBlock=substr($Block,$Pos,$EndIndex-$Pos); $Block=substr($Block,0,$Pos). Block($SubBlock,$BlockId). substr($Block,$EndIndex); } } else { $Pos=strpos($Block,'-->',$Pos); } } //Выделяем переменные for ($i=0; $i<count($Vars); $i++) { $Begin=$Vars[$i]; while ($Pos=strpos($Block,'<!--'.$Begin)) { $BeginIndex=$Pos; if ($EndIndex=strpos($Block,'-->',$BeginIndex)) { $Sub=substr($Block,$BeginIndex,$EndIndex-$BeginIndex+3); $Block=substr($Block,0,$BeginIndex). Variable($Sub,$i).substr($Block,$EndIndex+3); } } } return $Block; }
В качестве аргумента ей передается шаблон или его фрагмент (почему фрагмент? Потому что в нутри блока может быть еще один блок, то есть "внутренность" блока необходимо обработать отдельно). Далее она ищет вхождения строки <!--, если находит, то проверяет не является ли найденная строка тегом шаблонизатора, для этого напишем еще одну функцию - GetIsBlock($Block,$Offset), которой передаются тот же фрагмент шаблона и смещение в нутри его, то есть позицию найденного вхождения стоки <!--, возвращает она индекс имени блока из массива $Blocks или -1 если найденная строка не является тегом шаблонизатора.Код этой функции можно увидеть немного ниже. продолжаем разбирать шаблон. Если после проверки выясняется, что найденная строка - тег шаблонизатора, и является блочным, ищем его закрывающий тег. Выделяем подблок, то есть сами теги блока и внутренности, и передаем все это функции Block($Block, $BlockId), которая обрабатывает блок.
Теперь обещанная функция GetIsBlock($Block,$Offset), она простая поэтому думаю комментировать нет необходимости:
function GetIsBlock($Block,$Offset) { global $Blocks; // Выделяем имя блока $EndInd=strpos($Block,'-->',$Offset); if (($Offset+4)!=$EndInd) { $S=substr($Block,$Offset+4,$EndInd-($Offset+4)); $Bl=explode(' ',$S); $BlockName=$Bl[0]; for ($i=0; $i<count($Blocks); $i++) { if ($Blocks[$i][0]==$BlockName) { return $i; } } } return -1; }
Рассмотри работу функции Block($Block, $BlockId):
function Block($Block, $BlockId) { global $Blocks; $Ret=''; // Получаем параметры $BeginStr=$Blocks[$BlockId][0]; $BeginLength=4+strlen($BeginStr); $EndOperatorPos=strpos($Block,'-->'); $Operator=substr($Block,$BeginLength,$EndOperatorPos-$BeginLength); $Args=explode(' ',trim($Operator)); $EndBlockPos=strpos($Block,'<!--'.$Blocks[$BlockId][1].'-->'); $SubBlock=substr($Block,$EndOperatorPos+3,$EndBlockPos-$EndOperatorPos-3); if ($Blocks[$BlockId][2]) // блок не цикличный { if ($BeginStr($Args)) { $Ret.=ParseTmpl($SubBlock); } } else { while ($BeginStr($Args)) { $Ret.=ParseTmpl($SubBlock); } } return $Ret; }
Ей передаются фрагмент шаблона, содержащий рассматриваемый блок, и индекс блока в массиве $Blocks. Для начала в ней мы разбираем строку открывающего тега, на предмет параметров (это на всякий случай, а вдруг Вам необходимо вывести не все новости а какое-то количество). То есть если блок новостей начинается со строки:
<!--news 5-->
Где 5 - количество выводимых новостей.
Мы должны иметь возможность выделить именно определенное количество новостей. Далее выделяется "внутренность" блока в переменню $SubBlock, для дальнейшего парсирования. Далее в зависимости от результатов проверки на цикличность блока вызываем уже знакомую нам функцию ParseTmpl, либо при выполнении условия, либо в цикле до не выполнения условия. Условием является не нулевой результат выполнения функции одноименной с блоком.
Для каждого циклического блока необходимо создать функцию которая бы возвращала 0 или не 0 в зависимости от того нужно ли повторять разбор блока. Это может быть функция которая будет при каждом вызове выбирать очередную порцию данных из таблицы новостей и возвращать 1 если например количество выбранных новостей не больше количества вереданного ей в качестве одного из элементов массива аргументов блока.
Переменные.
Переменные будем хранить в массиве
$Vars=array('имя переменной');
Так же как и в случае с блоками создадим одноименные функции которые бы возвращали значения этих переменных (брали их, например, из базы данных). Вот участок функции ParseTmpl выделяющий переменные из шаблона и подставляющий их значения:
for ($i=0; $i<count($Vars); $i++) { $Begin=$Vars[$i]; while ($Pos=strpos($Block,'<!--'.$Begin)) { $BeginIndex=$Pos; if ($EndIndex=strpos($Block,'-->',$BeginIndex)) { $Sub=substr($Block,$BeginIndex,$EndIndex-$BeginIndex+3); $Block=substr($Block,0,$BeginIndex). Variable($Sub,$i).substr($Block,$EndIndex+3); } } }
И функция Variable:
function Variable($Block, $VarId) { global $Vars; $Ret=''; // Получаем параметры $BeginStr=$Vars[$VarId]; $BeginLength=4+strlen($BeginStr); $EndOperatorPos=strpos($Block,'-->'); $Operator=substr($Block,$BeginLength,$EndOperatorPos-$BeginLength); $Args=explode(' ',trim($Operator)); return $BeginStr($Args); }
Она похожа на функцию Block, но значительно проще.
Пример шаблона и функции блоков и переменных.
Шаблон вывода наших новостей:
<table> <!--news 5--> <tr> <td> [<b><!--ndate--></b>] <!--ntitle--> </td> </tr> <tr> <td> <!--nbody--> </td> </tr> <!--end_news--> </table>
А вот код для работы с блоками и переменными:
$QueryResult_News=0; $QueryRow_News=0; function news($Args) // Блок news { global $QueryResult_News; global $QueryRow_News; $Active=$Args[0]; if ($QueryResult_News==0) { $QueryResult_News=mysql_query("select * from news by ndate desc limit $Active"); } if ($QueryRow_News=mysql_fetch_array($QueryResult_News)) { return 1; } else { $QueryResult_News=0; return 0; } } function ndate($Args) // переменная ndate { global $QueryRow_News; if (isset($QueryRow_News['ndate'])) { return implode('.',array_reverse(explode('-',$QueryRow_News['ndate'],3))); } else { return ''; } } function ntitle($Args) // переменная ntitle { global $QueryRow_News; if (isset($QueryRow_News['title'])) { return $QueryRow_News['title']; } else { return ''; } } function nbody($Args) // переменная nbody { global $QueryRow_News; if (isset($QueryRow_News['body'])) { return $QueryRow_News['body']; } else { return ''; } }
Массивы $Blocks и $Vars:
$Blocks=array(array('news','news',0)) $Vars=array('ndate','ntitle','nbody');
Ну вот и все простой шаблонизатор готов, все что я хотел сделал, Вам же теперь его модернизировать и усовершенствовать до тех пор пока он не станет удовлетворять всем Вашим ожиданиям :)
Удачи, искренне Ваш elrevin.
Оставить комментарий
Комментарии
Зарание спасибо!