Как стать автором
Обновить

Шаблонизатор на php

Время на прочтение5 мин
Количество просмотров9.1K
Вообще писать особо не умею, обычно выходит только код, но постараюсь :)
Вот не прижилось во мне свойство использовать чужые готовые решения при разработке сайтов и по этому многое пишу сам, возможно по аналогии, но зато с полным пониманием и надеждой на то, что в будущем смогу разобраться в этом коде. Как-то получил вопрос мол зачем пишешь свой движок если есть куча готовых, бери и модернизируй для своей цели: подключай модули, настраивай и забивай бд. Ответить по сути не смог, но идею передал – нравится мне кодить да и вообще разбираться в новом.
Ну собственно о чём я. В очередной переделке своего движка решил уже написать что-то вроде шаблонизатора. Раньше был вариант, но настолько не удобный, что приходилось много править, за многим следить.

Почитал статьи на эту тему и как-то ничего нового не нашёл, кроме одной реализации.

Суть заключалась в том что шаблон представлял собой текстовый файл вида
[*IF(blabla):*]Вася пупкин present[*ELSE:*]Сайт закрыт на реконструкцию[*ENDIF*]
.............

И был скрипт php, который заменял [* на <? и *] на ?>
При том замена была простым str_replace(array('[*','*]'), array('<?','?>'))

После замены код сохранялся в php файл для кэширования. Если в дальнейшем код брался из файла то выполнение шаблона работало так
ob_start();
error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
include($templateCache);
error_reporting(E_ALL);
$text = ob_get_contents();
ob_end_clean();


если же в кэше файла для данного шаблона ещё нет, то так
ob_start();
error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
eval('?>'.$codeFromTemplate.'<?');
error_reporting(E_ALL);
$text = ob_get_contents();
ob_end_clean();


И как-то сначала мне этот метод даже понравился, но потом, во время дальнейшего использования нравился уже не так :) Чего-то не хватало, для более удобной реализации шаблонов.
Кстати, глобальные переменные не были доступные при "компиляции" шаблона, по этому был принят следующий шаг.
Внутри функции обработчика шаблона создавались переменные, которые используются в шаблоне.
Для этого был создан глобальный массив в котором ключ был именем переменной, а значение — значением переменной. После чего в функции выполнялся следующий код
foreach($templateVars as $k=>$v)
$$k = $v;


Ну так вот, решил я сделать этот шаблонизатор удобнее и посмотрел в сторону Smarty. Кто пользовался этим шаблонизатором, тот знает, что у него есть масса возможностей.
Скачал, посмотрел demo, побегал по коду и сделал следующую реализацию...
Сначала отказался от [* *] так как не самая удобная комбинация. Решил использовать { }
За тем стал думать над тем каким образом можно разбирать выражение
{include 'last.tpl'}
{include ('last.tpl', 'имя внутреннего шаблона')}

и пришёл к выводу, что лучше регулярных выражения ничего в голову не идёт.
Запустил RegexBuddy(удобная программка для работы с регулярными выражениями) и стал писать обработчик для шаблона include
В конце концов получилась вот такая строка
{\s*include\s*\({0,1}(\'{0,1}[$A-Za-z.0-9_\-]+\'{0,1})(?:(,{0,1}\s*\'{0,1}[$A-Za-z.0-9_\-]+\'{0,1})|\s*)\){0,1}\s*}

Возможно она не самая лучшая для данного варианта, но мои познания в регулярках не на столько высоки на данный момент :)
для обработки использовалась функция preg_replace
В качестве перaметров ей отдавалось регулярное выражение, строка <?php w_include(${1}${2}); ?> и содержимое шаблона.

w_include это функция описанная в скрипте для обработки шаблона, подключенного из опять же шаблона.

Собственно сижу я уже радуюсь, пишу обработчик для текста(так как может быть несколько строк) вида
{if условие}действие{else}другое действие{/if}
или
{if условие}действие{/if}

и даже получается это решение в одну строку и вроде бы всё хорошо, пока я не решаю в шаблоне заюзать вложенность условий.
Получилась следующая ситуация
{if $a}
  {if $b}
 1
  {/if}
2
{/if}

и шаблонизатор обработав текст, выдал такой результат
<? if($a){ ?>
{if $b}
1
<? } ?>
2
{/if}

То есть задуманное не получилось, но если подумать как работает preg_replace то и не могло получиться. Повторил этот шаблон в Smarty и заглянул в кэш. То что увидел и помогло завершить начатое.
Решение крылось в том, что бы использовать следующую конструкцию в замене
<? if($a): ?>
...
<? endif; ?>

По этому строка регулярного выражения была разбита на три
'/{\s*if\s*(.*?)\s*}/' => '<?php if(${1}): ?>'
'/{\s*else\s*}/' => '<?php else: ?>'
'/{\s*\/if\s*}/' => '<?php endif; ?>'


Вот собственно и всё :)
Если кому интересно, то привожу свой класс для данной реализации шаблонизатора.

class t_wls_templater{
protected $dirTemplate = '';
protected $templateVars = array();
protected $templateFile, $templateCache;
// ---------------- preg
protected $wls_templater_preg = array(

// {include ('tpl', 'f')} {include 'tpl', 't'} { include 'tpl' } etc...
'/{\s*include\s*\({0,1}(\'{0,1}[$A-Za-z.0-9_\-]+\'{0,1})(?:(,{0,1}\s*\'{0,1}[$A-Za-z.0-9_\-]+\'{0,1})|\s*)\){0,1}\s*}/' => '<?php $this->w_include(${1}${2}); ?>',
// {if true } exit(); else {foo();} {/if}
'/{\s*if\s*(.*?)\s*}/' => '<?php if(${1}): ?>',
'/{\s*else\s*}/' => '<?php else: ?>',
// {for ($i=0;$i<$num;$i++)}echo $foo;{/endfor}
'/{\s*for\s*\((.*?);(.*?);(.*?)\)}/' => '<? for(${1}; ${2}; ${3}): ?>',
'/{\s*\/(for|if)\s*}/' => '<?php end${1}; ?>',
// { all text }
'/{(.*?)}/' => '<?${1}?>'
);
// ---------------- end preg
public function __construct($dir=''){
$this->dirTemplate = empty($dir)?.'./templates/':$dir;
}

public function assign($name,$value){
$this->templateVars[$name] = $value;
}

public function parse($filename,$innerTemplateName=''){
// create vars what's used in templates
$THIS_TEMPLATE_NAME = $innerTemplateName;
$THIS_TEMPLATE_FILE = $filename;
$THIS_TEMPLATE_DIR = $this->dirTemplate;
foreach($this->templateVars as $k=>$v){
$$k = $v;
}
// end create
if(!is_file($this->dirTemplate.$filename)){
return false;
}
$this->templateFile = $this->dirTemplate.$filename;
$this->templateCache = './templates_cache/_'.$_tmp['name'].'_'.$filename.'.php';

$orig_time = filemtime($this->templateFile);

if(is_file($this->templateCache)){
$cash_time = filemtime($this->templateCache);
if($cash_time>$orig_time){
$this->_deb->addEvent('info', 'Use cache for template '.$filename.'');
ob_start();
error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
include($this->templateCache);
error_reporting(E_ALL);
$text = ob_get_contents();
ob_end_clean();
return $text;
}
}

$text = file_get_contents($this->templateFile);
foreach($this->wls_templater_preg as $preg=>$replace){
$text = preg_replace($preg, $replace, $text);
}
$f = fopen($this->templateCache,'w');
fwrite($f,$text);
fclose($f);
ob_start();
error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
eval('?>'.$text.'<?');
error_reporting(E_ALL);
$text = ob_get_contents();
ob_end_clean();
return $text;
}
protected function w_include($fname, $innerTemplateName = ''){
$_tmp = $this->parse($fname, $innerTemplateName);
echo $_tmp;
}
}
Теги:
Хабы:
-2
Комментарии44

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн