Просьба велосипедами не кидаться... Собстно простенький сабж для PHP5, с кэшом и плагинами, может кому пригодиться... PHP: <?php /** * Class Template * * * @author Anton Shevchuk <AntonShevchuk@gmail.com> * @link [url=http://anton.shevchuk.name]http://anton.shevchuk.name[/url] * * @access public * @package Template.class.php * @created Tue Jul 24 16:11:24 EEST 2007 */ // FIXME: remove public data!!! class Template { const TEMPLATE_OPTION_TPL_PATH = 'templates'; const TEMPLATE_OPTION_CACHE_PATH = 'cache'; const TEMPLATE_OPTION_PLUGIN_PATH = 'plugins'; const TEMPLATE_OPTION_DEBUG = 'debug'; const TEMPLATE_ERROR_FILE = 1; const TEMPLATE_ERROR_CACHE = 2; /** * This is Object Name * * @var string */ public $_name = 'main'; /** * Parent Object * * @var Template */ protected $_parent = null; /** * Children Objects * * @var array */ public $_children = array(); /** * Options * * array( * TEMPLATE_OPTION_TPL_PATH - path to template * TEMPLATE_OPTION_CACHE_PATH - path to cache * TEMPLATE_OPTION_PLUGIN_PATH - path to plugins * TEMPLATE_OPTION_DEBUG - debug enabled error reporting * ) * * @var array */ public $_options = array( self::TEMPLATE_OPTION_TPL_PATH => 'templates' , self::TEMPLATE_OPTION_CACHE_PATH => 'cache' , // self::TEMPLATE_OPTION_PLUGIN_PATH => 'plugins' , self::TEMPLATE_OPTION_DEBUG => false , ); /** * Template file * * @var string */ public $_template = null; /** * Variables stack * * @var array */ private $_vars = array(); /** * Cache * * @var bool */ private $_cache = false; /** * Cache ID * * @var string */ private $_cacheID = null; /** * Cache TTL (in seconds) * * @var integer */ private $_cacheTTL = 3600; /** * Constructor of Template * * @access public * @param array $aOptions */ function __construct($aOptions = array()) { $this->optionsSet($aOptions); } /** * Destructor of Template * * @access public */ function __destruct() { } /** * __set * * @access public * @param string $aName variable name * @param mixed $aValue variable value */ public function __set($aName, $aValue) { $this->varAdd($aName, $aValue); } /** * __get * * @access public * @param string $aName variable name */ public function __get($aName) { return $this->varGet($aName); } /** * __call * * redaclarate functions * * @access public * @param string $aMethod method name * @param array $aParams method params * @return mixed */ public function __call($aMethod, $aParams) { $aFunctionName = 'tpl_function_' . strtolower($aMethod); $aFunctionFlie = dirname(__FILE__) . DIRECTORY_SEPARATOR . self::TEMPLATE_OPTION_PLUGIN_PATH . DIRECTORY_SEPARATOR . 'function.' . strtolower($aMethod) . '.php'; array_unshift($aParams, &$this); if (!function_exists($aFunctionName) && is_file($aFunctionFlie)) { include_once $aFunctionFlie; return call_user_func_array($aFunctionName, $aParams); } elseif (function_exists($aFunctionName)) { return call_user_func_array($aFunctionName, $aParams); } else{ return false; } } /** * Manipulate options * * + optionsSet * + optionsGet */ /** * optionSet * * @access public * @param string $aOption * @param string $aValue */ public function optionSet($aOption, $aValue) { $this->_options[$aOption] = $aValue; } /** * optionsGet * * @access public * @param string $aOption */ public function optionGet($aOption) { if (isset($this->_options[$aOption])) { return $this->_options[$aOption]; } else { return false; } } /** * optionsSet * * @access public * @param array $aOptions */ public function optionsSet($aOptions = array()) { $this->_options = array_merge($this->_options, $aOptions); } /** * optionsGet * * @access public */ public function optionsGet() { return $this->_options; } /** * Manipulate objects * * + childAdd * + childSet * + childGet * + childDel * + childDelAll * + childDisplay */ /** * childAdd * * add child to stack * * @access public * @param mixed $aChildName object name * @param array $aOptions options * @return Template $aChild */ public function childAdd($aChildName, $aOptions = array()) { if (isset($this->_children[$aChildName])) { return $this->_children[$aChildName]; } else { $this->_children[$aChildName] = new Template($this->_options); if (empty($aOptions[self::TEMPLATE_OPTION_TPL_PATH])) { $aOptions[self::TEMPLATE_OPTION_TPL_PATH] = $this->_options[self::TEMPLATE_OPTION_TPL_PATH] . DIRECTORY_SEPARATOR . $this->_name . DIRECTORY_SEPARATOR ; // $aOptions[self::TEMPLATE_OPTION_CACHE_PATH] = $this->_options[self::TEMPLATE_OPTION_CACHE_PATH] . DIRECTORY_SEPARATOR . $this->_name . DIRECTORY_SEPARATOR ; } $this->_children[$aChildName] -> optionsSet($aOptions); $this->_children[$aChildName] -> _name = $aChildName; return $this->_children[$aChildName]; } } /** * childSet * * add child to stack * * @access public * @param mixed $aChildName object name * @param Template $aTemplate object * @return void */ public function childSet($aChildName, &$aTemplate) { if ($aTemplate instanceof Template) { $this->_children[$aChildName] =& $aTemplate; return true; } else { return false; } } /** * childGet * * get/create child from stack * * @access public * @param mixed $aChildName object name * @return void */ public function childGet($aChildName) { if (!isset($this->_children[$aChildName])) { $this->childAdd($aChildName); } return $this->_children[$aChildName]; } /** * childDel * * del child from stack * * @access public * @param mixed $aChildName object name */ public function childDel($aName) { if (isset($this->_children[$aChildName])) { unset($this->_children[$aChildName]); return true; } else { return false; } } /** * childDelAll * * clear stack * * @access public */ public function childDelAll() { $this->_children = array(); } /** * childDisplay * * display child * * @access public * @param mixed $aChild object name */ public function childDisplay($aChild) { if (is_array($aChild)) { $aChildName = array_shift($aChild); } else { $aChildName = $aChild; } if (!isset($this->_children[$aChildName])) { return false; } else { if (is_array($aChild) && sizeof($aChild) > 0) { $this->_children[$aChildName] -> childDisplay($aChild); } else { $this->_children[$aChildName] -> display(); } } } /** * Manipulate variables * * + varAdd * + varAddGlobal * + varGet * + varGetGlobal * + varDel * + varDelAll */ /** * varAdd * * add variable to stack * * @access public * @param string $aName variable name * @param mixed $aValue variable value */ public function varAdd($aName, $aValue) { $this->_vars[$aName] = $aValue; } /** * varDel * * del variable from stack * * @access public * @param string $aName variable name * @return void */ public function varDel($aName) { if (isset($this->_vars[$aName])) { unset($this->_vars[$aName]); return true; } else { return false; } } /** * varDelAll * * clear stack * * @access public */ public function varDelAll() { $this->_vars = array(); } /** * varGet * * get variable from stack * * @access public * @param string $aName variable name * @param mixed $aValue variable value * @return mixed */ public function varGet($aName) { if (isset($this->_vars[$aName])) { return $this->_vars[$aName]; } else { return $this->varGetGlobal($aName); } } /** * varAddGlobal * * add variable to stack * * @access public * @param string $aName variable name * @param mixed $aValue variable value * @return rettype return */ public function varAddGlobal($aName, $aValue) { if (!$this->_parent) { $this->varAdd($aName, $aValue); } else { $this->_parent->varAddGlobal($aName, $aValue); } } /** * varGetGlobal * * add variable to stack * * @access public * @param string $aName variable name * @param mixed $aValue variable value * @return rettype return */ public function varGetGlobal($aName) { if (!$this->_parent) { return false; } else { $this->_parent->varGet($aName); } } /** * Manipulate templates * * + tplSet * + tplDel */ /** * tplGet * * set template * * @access public * @return string template file */ public function tplGet() { return $this->_template; } /** * tplAdd * * set template * * @access public * @param mixed $aTemplateFile template file */ public function tplSet($aTemplateFile) { $aTemplate = $this->_options[self::TEMPLATE_OPTION_TPL_PATH] . $aTemplateFile; if (!file_exists($aTemplate)) { return false; } else { $this->_template = $aTemplate; return true; } } /** * tplDel * * remove template * * @access public */ public function tplDel() { $this->_template = null; } /** * Manipulate cache * * + cacheSet * + cacheId * + cacheIs */ /** * set cache * * @author dark * @class HSTemplateDisplay * @access public * @param mixed $aId * @param integer $aTime * @return void */ public function cacheSet($aId, $aTTL = 3600) { $this->_cache = true; $this->_cacheTTL = $aTTL; $this->cacheId($aId); try { if ( is_dir($this->_options[self::TEMPLATE_OPTION_CACHE_PATH] ) && is_writable($this->_options[self::TEMPLATE_OPTION_CACHE_PATH] )) { // all OK return true; } elseif ( is_dir($this->_options[self::TEMPLATE_OPTION_CACHE_PATH] ) && !is_writable($this->_options[self::TEMPLATE_OPTION_CACHE_PATH] )) { throw new Exception('Template Error: Cannot write directory "' . $this->_options[self::TEMPLATE_OPTION_CACHE_PATH] . '"'); } elseif (!@mkdir($this->_options[self::TEMPLATE_OPTION_CACHE_PATH] )) { throw new Exception('Template Error: Cannot create directory "' . $this->_options[self::TEMPLATE_OPTION_CACHE_PATH] . '"'); } } catch (Exception $e) { echo $e->getMessage(); return false; } } /** * set/get cache id * * @access public * @param mixed $aId * @return mixed */ public function cacheId($aId = null) { if ($aId) { if (is_array($aId)) { $this->_cacheID = implode("::", $aId); } else { $this->_cacheID = $aId; } } return $this->_cacheID; } /** * Check cache * * @access public * @return void */ public function cacheIs() { if ($this->cacheId() === null) { return false; } $aFileName = $this->_cacheFile(); if (!file_exists($aFileName)) { return false; } else { if ((filemtime($aFileName) + $this->_cacheTTL) < time()) { return false; } else { return true; } } } /** * create cache file * * @access private * @param string $aContent content for file * @return void */ private function _cacheCreate($aContent = '') { $aFileName = $this->_cacheFile(); @unlink($aFileName); // create file with cache $fp = fopen($aFileName, "at") or die(trigger_error('Template Error: Cannot create file "'. $aFileName . '"', E_USER_ERROR)); flock ($fp, LOCK_EX);// lock file rewind($fp); // rewind the position of a file pointer fwrite($fp, $aContent); // write data flock ($fp, LOCK_UN); // release the lock fclose($fp); // close file } /** * get cache file name * * @access private * @return string */ private function _cacheFile() { return $this->_options[self::TEMPLATE_OPTION_CACHE_PATH] . DIRECTORY_SEPARATOR . // $this->_name . DIRECTORY_SEPARATOR . $this->_name . '_' . md5($this->_name . '#' . $this->_cacheID) . '.html'; } /** * Manipulate output * * + display * + fetch */ /** * display * * @access public * @return rettype return */ public function display() { if ($this->_template) { $this->fetch(true); } } /** * fetch * * cetch all (or selected) template * * @access public * @param bool $aDisplay template name * @return rettype return */ public function fetch($aDisplay = false) { // $oldErrorReporting = error_reporting(); // // if ($this->_options[self::TEMPLATE_OPTION_DEBUG ]) { // error_reporting(E_ALL); // } else { // error_reporting(E_ALL ^ E_NOTICE); // } ob_start(); if ($this->_cache && $this->cacheIs()) { include $this->_cacheFile(); } else { include $this->_template; } $cache = ob_get_contents(); ob_end_clean(); if ($this->_cache && !$this->cacheIs()) { $this->_cacheCreate($cache); } // for cache system if ($aDisplay) { echo $cache; } else { return $cache; } // error_reporting($oldErrorReporting); } } ?> Пример плагина: PHP: <?php /** * tpl_function_percent * * count percent * * @access public * @param Template $Template * @param integer $aCount * @param integer $aTotal * @param integer $aPercent * @param string $aFormat * @return rettype return */ function tpl_function_percent(&$Template, $aCount, $aTotal, $aPercent = false, $aFormat = '') { // calculate percent or if (!$aPercent) { $tmp = ($aTotal / 100 ); if ($tmp != 0) { $aResult = $aCount / $tmp; } else { $aResult = 0; } } else { $aResult = ($aTotal / 100 ) * $aCount; } // output data if (empty($aFormat)) { return $aResult; } else { sprintf($aFormat, $aResult); } } ?> Пример использования: PHP: <?php require_once('Template.class.php'); /* Template initialization */ $options = array( Template::TEMPLATE_OPTION_TPL_PATH => dirname(__FILE__) . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR , Template::TEMPLATE_OPTION_CACHE_PATH => dirname(__FILE__) . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR , Template::TEMPLATE_OPTION_DEBUG => false, ); $Template = new Template($options); $Template -> field1 = 'field1'; $Template -> varAdd('field2', 'field2'); $Template -> tplSet('index.html'); $Child = $Template->childAdd('child'); $Child -> tplSet('child.html'); $Child1 = $Child->childAdd('child1'); $Child1 -> varAdd('time1', date('H:i:s')); $Child1 -> tplSet('child1.html'); $Child2 = $Child->childAdd('child2'); $Child2 -> varAdd('time2', date('H:i:s')); $Child2 -> tplSet('child2.html'); $Child2 -> cacheSet('child2', 5); $Template -> display(); ?> Пример файла шаблона ('index.html'): PHP: Percent: <?php echo $this->percent(5, 20); ?><br/> Field1: <?php echo $this->field1; ?><br/> Field2: <?php echo $this->field2; ?><br/> <?php $this->childDisplay('child'); ?> Дерево категорий будет типа: Код (Text): |\ cache |\ plugins | | - function.percent.php |\ templates | |\ main | | |\ child | | | | - child1.html | | | | - child2.html | | | - child.html | | - index.html | - index.php | - Template.class.php
PHP: $field2 = 'field2'; include 'templates/main/index.html'; $time1 = date('H:i:s'); include 'templates/main/child/child1.html'; Разве не кошерно? З.Ы. Я вообще велосипеды люблю, потому что если ты сам ничего не делаешь, значит ничему и не научишься. Но зачем же себе жизнь так сильно усложнять? ИМХО, конечно. upd ты полностью извратил ту идею Димы Смирнова, которую он нёс. Говорил-то он про easy, а тут hard с надстройкой .
1. не надо комментировать каждую строчку. 2. предвещаю тебе появление мозолей на подушечках пальцев ;-)
Кода много, толку мало. 1). Зачем такие длинные названия? set/append/get/load хватает выше крыши. Очистка нафиг вообще нужна? Крайне глупо набирать кучу HTML'a а потом внезапно его обнулять. Это уже грубые ошибки в проэктировании - такого не должно быть в принципе. Если надо перезаписать какую-то переменную - это прекрасно делает set. 2). Кешировщик в шаблонизаторе плохая идея. Почему? Потому что придёться использовать шаблонизаор там, где его быть не должно. До вызова метода/модуля. 3). Кешировщик в шабонизаторе не даёт той гибкости, которую может дать отдельный кешировщик. Кеширующий движок должен уметь работать с несколькими типами кеша - HTML держать на фаиловой системе можно, прочий стафф в XCache/APC и Memcache в зависимости от того, нужны ли данные на нескольких серверах или только локально. Можно и HTML кеш там же держать, если его не очень много или хватает памяти. Надеюсь идею поняли. Шаблонизатор должен уметь только 3 вещи: Принимать данные, отдавать и загружать шаблоны. ВСЁ. Больше там __Н-И-Ч-Е-Г-О__ быть не должно.
Psih Не соглашусь... мне лично от хорошего шаблонизатора еще нужны возможности параметризованной вставки вспомогательного шаблона и враппинг. Без этого верстать сложные дизайны становится реально неудобно. А если по-хорошему, так уважающий себя шаблонизатор должен еще поддерживать модификаторы, генераторы простых конструкций (типа списка <option> у <select>) и шаблонные комментарии.
Dagdamor 1). Помните я выкладывал свой движок? Парсер там простейший. Но сама структура позволяет делать сложные вещи. Всё что надо, это правильно по шаблонам расположить вызовы $parser->get(); и вовремя в нужные переменные подгрузить данные. http://blogs.boomtime.lv/Psih.html В чём фикус этой страницы - в том, что здесь 2 глобальных шаблона. Один layout сновной, а второй layout это где уже сам дневник. И каждый блок в этом большом блоке отдельный шаблон. Причём правая колонка строиться из вывода двух модулей. Вызываються они естественно сперва один, потом второй, только вот шаблоны там в перемешку проставленны и идут так: Код (Text): Blogs Blog Blog Blogs Хотя если заглянуть в контроллер, то вызова всего 2: PHP: <?php module('Blogs', RIGHT); module('Blog', RIGHT); ?> Так что при правильном проэктировании ничего лишнего не требуеться. А подгрузить дополнительный шаблон в другой шаблон - проще парёной репы: <?=$parser->load('template_name')?> - вот вам подгрузка другого шаблона прямо в шаблоне. Модификаторы? ЗАЧЕМ? DATE_FORMAT прекрасно делаеться в SQL. htmlspecialchars & co ? Сделайте это при раскрутке результата из базы, а в шаблоне просто надо просто ВЫВЕСТИ. В шаблоне должно быть МИНИМУМ КАКОГО-ЛИБО кода и МАКСИМУМ HTML'a (ну и JavaScript если надо)
Psih Форматирование даты - далеко не единственный пример модификатора. Да и не вижу я преимуществ в подготовке данных для шаблона на уровне SQL запросов. Во-первых, давай не будем кричать (аргументы от этого не станут более убедительными), во вторых - нет, неверно. Когда мы работаем в скрипте, мы не должны задумываться об особенностях вывода данных в HTML - это ипостась шаблонизатора, одна из его функций. Да и просто неудобно все данные, предназначенные для вывода в шаблоне, экранировать вручную. P.S. Сайт твой не открывается...
Насчет методов очистки переменных и тд - я тоже не особо вижу в них смысл - писал наверное уже по инерции... А вот кэширование вынесено в шаблонизатор, т.к. того требует разрабатываемая моим тимом CMF, данный шаблонизатор заточен под её нужды... И модификаторы, я считаю, должны быть в шаблонизаторе...
ustas Враппинг (обертывание) - это возможность писать такое (пишу на примере своего шаблонизатора): Код (Text): <insert:myBlock title="Ссылки по теме"> <a href="http://php.net">php.net</a><br> <a href="http://php.ru">php.ru</a><br> <a href="http://php.com.ua">php.com.ua</a><br> </insert:myBlock> где вспомогательный шаблон "myBlock" выглядит примерно так: Код (Text): <table border="1"> <tr><td><var:title></td></tr> <tr><td><var:content></td></tr> </table> Здесь <var:title> выводит заголовок блока (переданный ему при вызове), а <var:content> подставляет "обернутый" фрагмент шаблона. Получается красиво оформленный блок с нужным тебе содержимым.
ustas Ну, враппинг (wrapping, wrap=обертывать) - все-таки более-менее официальный термин Если твой шаблонизатор поддерживает такую возможность, это хороший плюс, для меня во всяком случае.
вопрос другой, myBlock должен содержать php код для вставки, или результат выполнения php-кода. если первое, есть api для буфера php кода, наподобие ob_start function compiler_wraper($act, $parser) { $attr = $parser->parseDirectiveAttributes(); if ($act == 1) parser->outputNew(); else $buff = parser->outputGet(); } {wraper атрибуты=блаблабла} любые команды, вложенные враперы, вставки и инклюды {/wraper} , если второе, то это банальный capture
ustas Враппер должен быть динамическим, конечно же. Т.е. должна быть возможность делать в нем условия, циклы и прочее на основе переданных ему данных, то же самое в обертываемом фрагменте. В твоих терминах - это должен быть "php код для вставки", да.