Сейчас пользуюсь этим. И вроде бы все есть, но хочется поддержки установки блочных переменных в цикле, кеширования (не только шаблонов), ... . Но пока я не представляю даже как это будет выглядеть, какие переменные будут. Предыдущие вещи (валидатор форм, класс доступа к БД через mysqli) было написать значительно легче. Итак, что хочется реализовать: * произвольные теги для открытия/закрытия тегов шаблона. Что б можно было написать как <-loop-> так и [:loop:]. Ну и ессно в настройках нужно будет выставить какие теги парсить. * кеширование шаблонов * полное кеширокание, это когда уже кешируется шаблон с подставленными переменными * ессно простое назначение переменных, типа $tpl->v('var', 'value of var'); шаблон <div>[:var1:]<div> * ветвления * загрузку шаблонов как из тела шаблона, так и через $tpl->load('path/to/template'); * циклы. Тут особо интересно: сейчас phparser способен делать циклы следущего вида: PHP: <?php $array[] = array('var1'=>'val1', 'var2'=>val2); $array[] = array('var1'=>'val3', 'var2'=>val4); $array[] = array('var1'=>'val5', 'var2'=>val6); $tpl->l('block', $array); Код (Text): [:loop block:] [:var1:] - [:var:] - [:var2]<br /> [:/loop:] Кто не понял - var - как бы глобальная переменная, которая была подставлена ранее. еще есть возможность делать вложенные циклы, но они не очень удобно сделаны. Что хочется: то же, что сейчас умеет phparser, но без тех вложенных циклов, которые сейчас в нем (через #) я сейчас работаю в одной конторе, так там такая штука: PHP: <?php foreach ((array)$_ai['categories_in'] as $_category) { // choose the router and params for URL generation $url = $this->_generateCategoryUrl($general_url, $_category); $tpl->addDataItem("ARTICLE.CATEGORIES_IN.CATEGORY.NAME", $_category['name']); $tpl->addDataItem("ARTICLE.CATEGORIES_IN.CATEGORY.URL", $url); } смысл в чем: метод addDataItem используется как для подстановки простых переменных, так и для циклов. Причем глубина неограничена, как я понял. По секрету скажу, что блоки тут используются в качестве ветвлений, а именно: Код (Text): <TPL_SUB:ARTICLE> <TPL_SUB:ARTICLE.TITLE> <TPL_SUB:ARTICLE.TEXT> <TPL_SUB:ARTICLE.CATEGORIES_IN> <ARTICLE.CATEGORIES_IN.NAME> </TPL_SUB:ARTICLE.CATEGORIES_IN> </TPL_SUB:ARTICLE> Если мы не подставим ни одной переменной в SUB-ARTICLE.CATEGORIES_IN внутри этого блока ничего не будет выведено. Итак, хочется поддержки этого, возможно что бы это работало через $tpl->v('', ''), а возможно через отдельный метод. В phpBB тоже есть блочные переменные. Отличие их в том, варианте, который приведен выше: PHP: <?php for ($i = 0; $i < 2; $i++) { $template->assign_block_vars('somerow', array( 'VAR1' => "Just a string with the value of \$i: $i", 'VAR2' => ($i * 4), )); } Передается ассоциативный массив. Если в верхнем шаблонизаторе мы указывали для каждой переменной Label, то тут можно просто задать массив. Итак, хочется поддержка всех этих 3-х видов задания циклов. Реально ли такое организовать? + возможно будут такие вкустности, как вызов какого-то объекта из шаблона, но это потом. Ну и с чего начинать писалово? как хранить переменные, как их подставлять?
У меня шаблонизатор устроен без логических блоков. Просто мне это не нужно. А вот кеширование реализовал именно по принципу подстановки, только производится блоковая замена не контентом, а кодом, но в первый раз (если шаблона нет в кеше) выводить приходится уже парсированную версию, а не загружать сгенерированную из кеша (само собой).
возможна "иерархия" классов. 1 - Класс через который все манипуляции происходят (мы назначаем переменные, блоки) 2 - Класс, который компилирует шаблон, и он будет вызван из класса выше 3 - Класс, который отвечает за кеширование, и он будет вызван из класса выше 4 - Класс, который отвечает за полное кеширование, и он будет вызван из класса 1.
У меня так (можно проследить логику): PHP: <?php /** * Загружаем шаблон. * При этом в памяти может быть одна и только одна копия шаблона */ $html->load('index'); /** * Устанавливаем блоки для замены. * Их устанавливать можно также в цикле * Блоки, сами по себе, имеют логическую структуру */ $html->block('${index->block}', 'Это заменяющий текст'); /** * Собираем шаблон и одновременно кешируем его * закрытым методом AddCache */ $html->build('index'); /** * А вот компоновка и имена блоков будут логически * зависеть от имени собранного блока (index, news). * Т.е структура назначается автоматически */ /** * Дублируем этот же шаблон в выводе. * Кешировать он уже не будет */ $html->block('${main->second}', '...'); $html->build('index'); // Добавление к уже существующей копии /** * При создании другой копии вызывается метод free */ //$html->load $html->free(); // Мануальный вызов // ...
Mr.M.I.T., ты невнимательно прочёл. Флоппик написал, что это имхо и что можно кэшировать на стороне, а не в самом шаблонизаторе. Иногда так, иногда сяк, иногда этак.
lexa тоже имхо, компилированное нужно кешировать в самом шаблонизаторе, а вот Полное кеширование на стороне
Mr.M.I.T. НЕЕЕЕТ основная их задача - циклы. Вторичное использование - ветвления, но они неудобны для этого.\ По теме: предложите хотя бы структуру класса. Переменные для циклов и простых подстановок как хранить?
потому что они по-разному будут подставляться и в разных методах. Ну набросай примерно структуру класса с методами-заглушками и мало-мальскими комментами.
Koc мм мне вообще такой синтаксис нравится =) PHP: <? $tpl=new TPL(); $tpl->SetVar("arr",array("val1","val2","val3")); $tpl->SetVar(array("var1"=>"value_var1","var2"=>"value_var2")); $tpl->Load("test.tpl"); ?> test.tpl HTML: arr:<br> {for($i=0,$c=count($arr);$i<$c;$i++)} #{$i} - {$arr[$i]}<br> {/for} its var1 - {$var1}<br> its var2 - {$var2} или php-натив вот нативный вариант шаблонизатора (не тестился) PHP: <? class TPL_Driver_Native implements TPL_Interface { private $errorH="self::_DefErrorHandler"; private $cacheH=false; private $vars; // Временные переменные private static $globals; // постоянные переменные protected $cnf=array("tpl_dir"=>"tpl","tpl_skin"=>"default"); function __construct($cnf){ $this->cnf=$cnf; } // Добавляет переменную в шаблон function SetVar($var,$value=false){ if(!is_array($var)){ $this->vars[$var]=$value; }else{ foreach($var as $key=>$val) $this->SetVar($key,$val); } } // Добавляет постоянную переменную function SetGlobals($var,$value=false){ if(!is_array($var)){ self::$globals[$var]=$value; }else{ foreach($var as $key=>$val) $this->SetGlobals($key,$val); } } // Загружает шаблон $tplname, возвращает готовый результат function Load($tplname,$cacheIdent=false){ if($cacheIdent)$cacheIdent=$tplname; if($res=$this->CheckCacherLoad($cacheIdent))return $res; $res=$this->LoadData($tplname); $this->CheckCacherSave($cacheIdent,$res); return $res; } // Удаляет переменные function Clear($vars=array()){ if(empty($vars)) { $this->vars=array(); }else{ for($i=0,$c=count($vars);$i<$c;$i++){ unset($this->vars[$vars[$i]]); } } return true; } // Загрушает шаблон, подставляет переменные // Может быть использован для вставки шаблона в шаблон // как-то так < ? echo $this->LoadData("sub_test.tpl"); ? > function LoadData($tplname){ static $cache; if(!$cache)$cache=array(); $md=md5($tplname); if(!$cache[$md]){ $tplname=$this->GetTplDir($tplname); if(is_file($tplname)){ $fp=@file_get_contents($tplname) or $this->Error("Невозможно загрузить $tplname",__METHOD__,__CLASS__,__LINE__); if(get_magic_quotes_gpc())$fp=stripslashes($fp); $cache[$md]=$fp; }else $this->Error("$tplname не является файлом или не существует",__METHOD__,__CLASS__,__LINE__); } return $this->EvalData($cache[$md]); } function setErrorHandler($name){ if(function_exists($name)){ $this->errorH=$name; }else{ $this->Error("Функция ($name) не существует",__METHOD__,__CLASS__,__LINE__); } return false; } function setCacheHandler($name){ if(function_exists($name)){ $this->cacheH=$name; }else{ $this->Error("Функция ($name) не существует",__METHOD__,__CLASS__,__LINE__); } return false; } private function GetTplDir($tplname){ return $_SERVER["DOCUMENT_ROOT"].'/'.$this->cnf['tpl_dir'].'/'.$this->cnf['tpl_skin'].'/'.$tplname; } // Подставляет переменные private function EvalData($__data){ if($this->vars['__data'])unset($this->vars['__data']); @ob_start(); extract($this->vars); $_GLOBALS=self::$globals; // =) чтобы не писать self::$globals eval("?>".$__data."<?"); $res=ob_get_contents(); ob_end_clean(); return $res; } private function Error($msg,$m,$c,$l){ $info=array("msg"=>$msg,"method"=>$m,"class"=>$c,"line"=>$l); eval($this->errorH.'($msg,$info);'); } private static function _DefErrorHandler($msg,$info){ trigger_error("$msg <br> Ошибка в классе {$info['class']} на строке {$info['line']}",E_USER_ERROR); } private function CheckCacherSave($tplaname,$res){ if(function_exists($this->cacheH)){ $hash=md5($this->cnf['tpl_skin'].$tplname); eval($this->cacheH.'($hash,$res);'); } return false; } private function CheckCacherLoad($tplname){ if(function_exists($this->cacheH)){ $hash=md5(($this->cnf['tpl_skin'].$tplname); eval('$res='.$this->cacheH.'($hash,false);'); return $res; } return false; } } ?>
Всё, вы меня достали своими шаблонизаторами. Вот вам тру код: PHP: <?php /* * Templates parser class * @copyright [email=Psih@php.ru]Psih@php.ru[/email] [email=Luge@php.ru]Luge@php.ru[/email] */ class Parser{ /** * Template name array. * * @var array */ private $_template; /** * Variable array * * @var array */ private $_variables; /** * Global variable array * * @var array */ private $_global; /** * Constructor * * @return void */ public function __construct() { $this->_template = array(); $this->_variables = array(); $this->_global = array(); } /** * Set template variable * * @param string $var_name Name, under witch data will be stored in internal associative array * @param mixed $data * @param bool $global If is set to "true", then global variable is created. This means it doesn't get deleted when fetched. */ public function set($var_name, $data, $global = false) { $this->{$global ? '_global' : '_variables'}[$var_name] = $data; } /** * Load template by include and return processed string. * If file wasn't found - give a fatal error. * * @param string $file * @return string */ public function load($file) { if (file_exists($file)) { ob_start(); include($file); return ob_get_clean(); }else { trigger_error('File '.$file.' doesn\'t exist', E_USER_ERROR); } } /** * Appends data to existing variable. If variable doesn't exists - creates it. * Works only with string or int type data. * * @param string $var_name Name, under witch data will be stored in internal associative array * @param string $data * @param bool $global If is set to "true", then global variable is created. This means it doesn't get deleted when fetched. * @return void */ public function append($var_name, $data, $global = false){ $arr = $global ? '_global' : '_variables'; if (isset($this->{$arr}[$var_name])){ $this->{$arr}[$var_name] .= $data; } else { $this->{$arr}[$var_name] = $data; } } /** * Get stored data by key name. If data isn't global, it get's deleted after first access. * Global variables never get deleted * * @param string $var_name * @param bool $global * @return mixed */ public function get($var_name, $global = false) { if ($global && isset($this->_global[$var_name])) { return $this->_global[$var_name]; } elseif (isset($this->_variables[$var_name])) { $var = $this->_variables[$var_name]; unset($this->_variables[$var_name]); return $var; } return null; } /** * Check if variable with such key exists * * @param string $var_name * @param bool $global * @return bool */ public function is_set($var_name, $global = false) { return (!empty($this->{$global ? '_global' : '_variables'}[$var_name])) ? true : false; } }
они ж на пару работают... Psih Че то не понял логику работы. Как переменные превращаются в переменные шаблона? У меня например происходит extract...
Mr.M.I.T. kostyl <?php echo get('var_name')?> в шаблоне, для этого в библиотеке функций есть следующее: PHP: <?php /* short parser functions */ function get($name,$global=false) { return Core::getInstance('parser',true)->get($name,$global); } function is_set($name,$global=false) { return Core::getInstance('parser',true)->is_set($name,$global); } Экстракт не плох,я просто его не юзаю: 1). Он действительно мусорит в глобальной области видимости => более долгий lookup, нету гарантии что не пересекутся имена переменных. 2). Не позволяет сделать супер-глобальную переменную для шаблонов (это наверно главный минус). 3). У меня данные при фетче тут-же чистятся по умолчанию, что снижает memory usage. А вообще тут устройство самого движка и философия KISS тесно связаны, когда можно пощупать весь движок целиком становится понятно что к чему. Luge вроде нравится, я даже не слышал ни разу предложений что-то сильно менять