Эта тема на forum.dklab.ru


Ksnk: карликовый шаблонизатор
Как-то приспичило сделать маленький проект. "Мух от котлет" отделять уже привык настолько, что "смешанный" код вызывает отвращение, а прикручивать какого-то "монстра" к паре скриптов - душит жаба. Вот, разродился карликовым шаблонизатором.
Зачем оно нужно?
Для маленьких проектов. Весит ~1к с комментариями, а если выкинуть комментарии - занимает 30 строк кода.
Что он умеет?
Понимает переменные вида {name}, точнее, умеет их заменять. Умеет "выкусывать" дополнительные строки шаблона, отмеченные скобками <!--@name-->xxx<!--name@--> из основного текста, и обзывать их дополнительными строками шаблона.
Вот - пример шаблона, файл, к примеру, "admin.tpl":<body>
{mode}
<!--@form-->
<form method="post" action=''>
<table style="width:800px;table-layout:fixed;">
<col width=100>
<col width=49%>
<col width=49%>
</td>

</tr>

{records}
<!--@recordi-->
<tr>
<th>{name}</th>
<td colspan=2><input class='long' name='value[{name}]' type='text' value='{value}'></td>
</tr>
<!--recordi@-->
<!--@recordt-->
<tr>
<th>{name}</th>
<td colspan=2><textarea name='value[{name}]' class='long' >{value}</textarea></td>
</tr>
<!--recordt@-->
<tr>
<td colspan=3 align='center'>{resume}</td>
</tr>
<tr>
<td colspan=3 align='center'><input type='submit' value='Ok' name='Ok'></td>
</tr>
</table>
</form>
<!--form@-->
<div id="debug">{debug}</div>
</body> При обработке этого шаблона будут сформированы строки "admin.tpl" с переменной {mode}, admin.tpl#form - {records}, admin.tpl#form#recordi - {name},{value}, admin.tpl#form#recordt- {name},{value}. Вывод информации с помощью шаблонизатора может выглядеть, к примеру, так
define ('ADMIN_TEMPLATE','admin.tpl');

/*
* вызов без дополнительных параметров - просто чтение файла.
*/
smart_template(ADMIN_TEMPLATE); // прочитали файл - запомнили все в кэше

foreach($variables as $v=>$value) {
$records.=smart_template(
ADMIN_TEMPLATE.'#form#record'.(strlen($value)<=20?'i':'t'),
array('name'=>$v,'value'=>htmlspecialchars($value))
);
}
$form_param['records']=$records;

/*
* выводим форму
*/
echo smart_template(ADMIN_TEMPLATE,array('mode'=>
smart_template(ADMIN_TEMPLATE.'#form',$form_param)));

или, к примеру, так

smart_template(ADMIN_TEMPLATE); // прочитали файл - запомнили все в кэше
ob_start(create_function('$mode',
'return smart_template(ADMIN_TEMPLATE,
array("mode"=>$mode));')
) ;
...
switch (...)
case (...)
echo 'Все пропало! Все пропало шеф!!!!' ;
exit;
case (...)
echo smart_template(ADMIN_TEMPLATE.'#form',$form_param));
exit;
...

А теперь - собственно он сам. Заводить класс посчитал слишком сильным ходом, для 30 строк. К тому-же для объекта, существующего в единственном экземпляре вполне хватит и функции со static - переменными внутри. <?php
/*
* карликовый шаблонизатор
* by ksnk <http://forum.dklab.ru/users/ksnk>
* ВАЖНО!!!!
* Первый вызов обязан быть smart_template(имя-существующего-файла-шаблона)
* для того, чтобы строки аккуратно прочитались.
*
* PREG, для обработки его sprintf'ом
*/
define ('SMART_PREG','~<!--@(%s)-->(.*)<!--\\1@-->~Us');

function smart_template($idx,$par=null,$str=null) {
static $templates =array();
if(!isset($templates[$idx])) {
/*
* конструктор - читаем файл или берем параметр
*/
$contents=(!is_null($str)?$str
:(is_file($idx)?file_get_contents($idx):''));
$templates[$idx]=preg_replace(sprintf(SMART_PREG,'.*'),'',$contents);
while (preg_match(sprintf(SMART_PREG,'.*'),$contents,$m)) {
smart_template($idx.'#'.$m[1],null,$m[2]);
$contents=preg_replace(sprintf(SMART_PREG,$m[1]),'',$contents);
}
}
/*
* вывод шаблонной строки
*/
if (!is_null($par)) {
$s= isset($templates[$idx])?$templates[$idx]
:'Oops! template "'.$idx.'" missing :(';
$preg = array();
$repl = array();
foreach($par as $k=>$v) {
$preg[] = '/{'.$k.'}/'; // надо бы вытащить наружу :(
$repl[] = $v;
}
return preg_replace($preg,$repl,$s);
}
}
?>
P.S. поправлен шаблон, убрана функция pps.
Юрий Насретдинов:
Ksnk
Поменьше бы регов Вы использовали... И более бы ясный интерфейс сделали :).
Ksnk:
Юpий Насрeтдинов
Ну, в оправдание :) могу привести следующие соображения:
- со str_replace'ом это бы работало, конечно побыстрее, но предполагалось(изначально) расширить синтаксис переменных до {name : условие : еще-чего-нибудь...} для какой-нибудь пользы, определяемой составителем шаблона. Пока это осталось для "додумать после".
- проекты (и шаблоны) также предполагаются небольшие, так что сильно выиграть на этом в скорости все равно не получится.
- интерфейс - да, мутноват и неочевиден. Надо над этим подумать. С другой стороны - это можно рассматривать как спортивное достижение - шаблонизатор из одной функции. ;-)
Юрий Насретдинов:
предполагалось(изначально) расширить синтаксис переменных до {name : условие : еще-чего-нибудь...} для какой-нибудь пользы, определяемой составителем шаблона. Пока это осталось для "додумать после".
Это похлеще Smarty получается...
проекты (и шаблоны) также предполагаются небольшие, так что сильно выиграть на этом в скорости все равно не получится.
В небольшом проекте шаблонизатор и не потребуется...
шаблонизатор из одной функции.
Вообще говоря, из двух ;)
jiraff: Re: карликовый шаблонизатор
function smart_template($idx,$par=null,$str=null)
Одна функция.Или Вы посчитали с main()? =)
Ksnk:
В небольшом проекте шаблонизатор и не потребуется...
C этим я, вероятно, не соглашусь. Если к проекту приходится возвращаться снова и снова, впихивая его в разные места, отделить PHP код от HTML красивостей необходимо... Да и просто доводить-дизайн-до-ума-по-месту становится значительно проще. Впрочем - это напоминает агитацию "за савецкую власть" ;-)
"шаблонизатор не потребуется" - imho, идет от убеждения, что шаблонизатор - велик и страшен, как Smarty. А так - "+30 строк" - совсем не страшно, а величия вполне должно хватить для несложных потребностей ;-)
Юрий Насретдинов:
function pps(&$x,$def='')
а вот вторая...
Ksnk:
а вот вторая...
Ну, формально это, конечно, так. Функция, правда, широко используется мной.
Хотя, за ради спорта, нужно переписать без ее использования... Код, практически, не увеличится.

if(!$contents=pps($str))
if (is_file($idx))
$contents=file_get_contents($idx);
заменить на

$contents=(!is_null($str)?$str
:(is_file($idx)?file_get_contents($idx):''));

$s = pps($templates[$idx],'Oops! template "'.$idx.'" missing :(');
заменить на
$s = isset($templates[$idx])?$templates[$idx]:'Oops! template "'.$idx.'" missing :(';
P.S.
Обновил текст описания сверху.
Rumata:
может заменить
~<!--@(%s)-->(.*)<!--\\1@-->~Us

на

/<!--@(\w+)--> ( (?: (?! <!--\1@--> ). )* ) <!--\1@-->/x
Ksnk:
Rumata
Это такой способ обойтись без модификатора U?
Что-то в этом есть, однако не будет ли Ваш рег работать дольше моего? К тому-же без модификатора s мне не удалось его заставить работать для "скобок", расположенных на нескольких строках.
P.S.
Протестировав на несложном примере я получил некоторый выигрыш в скорости моего прега перед вашим, причем при увеличении "внутренности", окруженной такими скобками, преимущество моего становится все более и более заметным. Так что пока - не убедили :-)
Rumata:

<!--@abc-->
text 1
<!--abc@-->
text 2
<!--@abc-->
text 3
<!--abc@-->


если тестировали на этом примере и ваш рег на нем работает корректно, то мое предложение снимается
Ksnk:
Вот такой тест я соорудил. Вроде все преги написаны верно. Никакой разницы, кроме скорости выполнения - не вижу. А как предполагается такие строки обрабатывать? Склеивать их или как сейчас - игнорировать хвосты?
$src="<!--@abc-->
text 1
<!--abc@-->
text 2
<!--@abc-->
text 3
<!--abc@-->";

$reg0='~<!--@(\w+)-->(.*)<!--\\1@-->~Us';
$reg1='/<!--@(\w+)--> ( (?: (?! <!--\\1@--> ). )* ) <!--\\1@-->/xs';

foreach(array($reg0,$reg1) as $v) {
preg_match($v,$src,$m);
$y= preg_replace($v,'',$src);
printf('##preg_match## m1-"%s",m2-"%s" ##preg_replace## "%s" --- ',$m[1],$m[2],$y);
mkt();

for ($i=0;$i<10000;$i++){
$y= preg_replace($v,'',$src);
}
printf("we spent %f sec<br>",mkt());
}
результат:

##preg_match## m1-"abc",m2-" text 1 " ##preg_replace## " text 2 " --- we spent 0.296894 sec
##preg_match## m1-"abc",m2-" text 1 " ##preg_replace## " text 2 " --- we spent 0.341001 sec

Эта тема на forum.dklab.ru