За последние 24 часа нас посетили 20755 программистов и 1109 роботов. Сейчас ищет 371 программист ...

шаблонизатор [нужен алгоритм]

Тема в разделе "Решения, алгоритмы", создана пользователем Koc, 16 май 2009.

  1. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    Сейчас пользуюсь этим. И вроде бы все есть, но хочется поддержки установки блочных переменных в цикле, кеширования (не только шаблонов), ... .

    Но пока я не представляю даже как это будет выглядеть, какие переменные будут. Предыдущие вещи (валидатор форм, класс доступа к БД через mysqli) было написать значительно легче.

    Итак, что хочется реализовать:

    * произвольные теги для открытия/закрытия тегов шаблона. Что б можно было написать как <-loop-> так и [:loop:]. Ну и ессно в настройках нужно будет выставить какие теги парсить.
    * кеширование шаблонов
    * полное кеширокание, это когда уже кешируется шаблон с подставленными переменными
    * ессно простое назначение переменных, типа $tpl->v('var', 'value of var'); шаблон <div>[:var1:]<div>
    * ветвления
    * загрузку шаблонов как из тела шаблона, так и через $tpl->load('path/to/template');
    * циклы. Тут особо интересно:
    сейчас phparser способен делать циклы следущего вида:
    PHP:
    1.  
    2.         <?php
    3.         $array[] = array('var1'=>'val1', 'var2'=>val2);
    4.         $array[] = array('var1'=>'val3', 'var2'=>val4);
    5.         $array[] = array('var1'=>'val5', 'var2'=>val6);
    6.        
    7.         $tpl->l('block', $array);
    8.  
    Код (Text):
    1.  
    2.         [:loop block:]
    3.             [:var1:] - [:var:] - [:var2]<br />
    4.         [:/loop:]
    Кто не понял - var - как бы глобальная переменная, которая была подставлена ранее.
    еще есть возможность делать вложенные циклы, но они не очень удобно сделаны.

    Что хочется:
    то же, что сейчас умеет phparser, но без тех вложенных циклов, которые сейчас в нем (через #)

    я сейчас работаю в одной конторе, так там такая штука:
    PHP:
    1.             <?php
    2.             foreach ((array)$_ai['categories_in'] as $_category) {
    3.                 // choose the router and params for URL generation
    4.                 $url = $this->_generateCategoryUrl($general_url, $_category);
    5.  
    6.                 $tpl->addDataItem("ARTICLE.CATEGORIES_IN.CATEGORY.NAME", $_category['name']);
    7.                 $tpl->addDataItem("ARTICLE.CATEGORIES_IN.CATEGORY.URL", $url);
    8.             }
    смысл в чем: метод addDataItem используется как для подстановки простых переменных, так и для циклов. Причем глубина неограничена, как я понял.
    По секрету скажу, что блоки тут используются в качестве ветвлений, а именно:
    Код (Text):
    1.  
    2.             <TPL_SUB:ARTICLE>
    3.                 <TPL_SUB:ARTICLE.TITLE>
    4.                 <TPL_SUB:ARTICLE.TEXT>
    5.                
    6.                 <TPL_SUB:ARTICLE.CATEGORIES_IN>
    7.                     <ARTICLE.CATEGORIES_IN.NAME>
    8.                 </TPL_SUB:ARTICLE.CATEGORIES_IN>
    9.             </TPL_SUB:ARTICLE>
    Если мы не подставим ни одной переменной в SUB-ARTICLE.CATEGORIES_IN внутри этого блока ничего не будет выведено.

    Итак, хочется поддержки этого, возможно что бы это работало через $tpl->v('', ''), а возможно через отдельный метод.


    В phpBB тоже есть блочные переменные.
    Отличие их в том, варианте, который приведен выше:
    PHP:
    1.  
    2.         <?php
    3.         for ($i = 0; $i < 2; $i++)
    4.         {
    5.             $template->assign_block_vars('somerow', array(
    6.                 'VAR1' => "Just a string with the value of \$i: $i",
    7.                 'VAR2' => ($i * 4),
    8.             ));
    9.         }
    10.  
    Передается ассоциативный массив. Если в верхнем шаблонизаторе мы указывали для каждой переменной Label, то тут можно просто задать массив.

    Итак, хочется поддержка всех этих 3-х видов задания циклов. Реально ли такое организовать?
    + возможно будут такие вкустности, как вызов какого-то объекта из шаблона, но это потом.


    Ну и с чего начинать писалово? как хранить переменные, как их подставлять?
     
  2. чисто имхо, в самом шаблонизаторе это не нужно, это легко реализовать отдельно и встроить
     
  3. Apple

    Apple Активный пользователь

    С нами с:
    13 янв 2007
    Сообщения:
    4.984
    Симпатии:
    2
    У меня шаблонизатор устроен без логических блоков.
    Просто мне это не нужно.
    А вот кеширование реализовал именно по принципу подстановки, только производится блоковая замена не контентом, а кодом, но в первый раз (если шаблона нет в кеше) выводить приходится уже парсированную версию, а не загружать сгенерированную из кеша (само собой).
     
  4. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    возможна "иерархия" классов.
    1 - Класс через который все манипуляции происходят (мы назначаем переменные, блоки)
    2 - Класс, который компилирует шаблон, и он будет вызван из класса выше
    3 - Класс, который отвечает за кеширование, и он будет вызван из класса выше
    4 - Класс, который отвечает за полное кеширование, и он будет вызван из класса 1.
     
  5. Apple

    Apple Активный пользователь

    С нами с:
    13 янв 2007
    Сообщения:
    4.984
    Симпатии:
    2
    У меня так (можно проследить логику):

    PHP:
    1. <?php
    2.  
    3. /**
    4.  * Загружаем шаблон.
    5.  * При этом в памяти может быть одна и только одна копия шаблона
    6.  */
    7. $html->load('index');
    8.  
    9. /**
    10.  * Устанавливаем блоки для замены.
    11.  * Их устанавливать можно также в цикле
    12.  * Блоки, сами по себе, имеют логическую структуру
    13.  */
    14. $html->block('${index->block}', 'Это заменяющий текст');
    15.  
    16. /**
    17.  * Собираем шаблон и одновременно кешируем его
    18.  * закрытым методом AddCache
    19.  */
    20. $html->build('index');
    21.  
    22. /**
    23.  * А вот компоновка и имена блоков будут логически
    24.  * зависеть от имени собранного блока (index, news).
    25.  * Т.е структура назначается автоматически
    26.  */
    27.  
    28. /**
    29.  * Дублируем этот же шаблон в выводе.
    30.  * Кешировать он уже не будет
    31.  */
    32. $html->block('${main->second}', '...');
    33. $html->build('index'); // Добавление к уже существующей копии
    34.  
    35. /**
    36.  * При создании другой копии вызывается метод free
    37.  */
    38. //$html->load
    39.  
    40. $html->free(); // Мануальный вызов
    41.  
    42. // ...
     
  6. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    флоппик
    как это не нужно компилированное кешировать?
     
  7. lexa

    lexa Активный пользователь

    С нами с:
    22 июл 2007
    Сообщения:
    1.746
    Симпатии:
    0
    Адрес:
    Санкт-Петербург
    Mr.M.I.T., ты невнимательно прочёл. Флоппик написал, что это имхо и что можно кэшировать на стороне, а не в самом шаблонизаторе. Иногда так, иногда сяк, иногда этак. :)
     
  8. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    lexa
    тоже имхо, компилированное нужно кешировать в самом шаблонизаторе, а вот Полное кеширование на стороне
     
  9. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    if ... else ? ;)
     
  10. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    Mr.M.I.T.
    НЕЕЕЕТ

    основная их задача - циклы. Вторичное использование - ветвления, но они неудобны для этого.\


    По теме:
    предложите хотя бы структуру класса. Переменные для циклов и простых подстановок как хранить?
     
  11. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    Koc
    зачем разделять переменные для циклов и простых подстановок?
     
  12. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    потому что они по-разному будут подставляться и в разных методах.
    Ну набросай примерно структуру класса с методами-заглушками и мало-мальскими комментами.
     
  13. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    Koc
    мм мне вообще такой синтаксис нравится =)
    PHP:
    1. <?
    2. $tpl=new TPL();
    3. $tpl->SetVar("arr",array("val1","val2","val3"));
    4. $tpl->SetVar(array("var1"=>"value_var1","var2"=>"value_var2"));
    5. $tpl->Load("test.tpl");
    6. ?>
    test.tpl
    HTML:
    1. arr:<br>
    2. {for($i=0,$c=count($arr);$i<$c;$i++)}
    3.      #{$i} - {$arr[$i]}<br>
    4. {/for}
    5. its var1 - {$var1}<br>
    6. its var2 - {$var2}
    или php-натив

    вот нативный вариант шаблонизатора (не тестился)

    PHP:
    1. <?
    2. class TPL_Driver_Native implements TPL_Interface {
    3.    private $errorH="self::_DefErrorHandler";
    4.    private $cacheH=false;
    5.    private $vars; // Временные переменные
    6.    private static $globals; // постоянные переменные
    7.    protected  $cnf=array("tpl_dir"=>"tpl","tpl_skin"=>"default");
    8.    function __construct($cnf){
    9.        $this->cnf=$cnf;
    10.    }
    11.    // Добавляет переменную в шаблон
    12.    function SetVar($var,$value=false){
    13.        if(!is_array($var)){
    14.           $this->vars[$var]=$value;
    15.        }else{
    16.           foreach($var as $key=>$val) $this->SetVar($key,$val);
    17.        }
    18.    }
    19.   // Добавляет постоянную переменную
    20.     function SetGlobals($var,$value=false){
    21.        if(!is_array($var)){
    22.           self::$globals[$var]=$value;
    23.        }else{
    24.           foreach($var as $key=>$val) $this->SetGlobals($key,$val);
    25.        }
    26.    }
    27.   // Загружает шаблон $tplname, возвращает готовый результат
    28.    function Load($tplname,$cacheIdent=false){
    29.        if($cacheIdent)$cacheIdent=$tplname;
    30.        if($res=$this->CheckCacherLoad($cacheIdent))return $res;
    31.        $res=$this->LoadData($tplname);
    32.        $this->CheckCacherSave($cacheIdent,$res);
    33.        return $res;
    34.    }
    35.    // Удаляет переменные
    36.    function Clear($vars=array()){
    37.        if(empty($vars)) {
    38.           $this->vars=array();
    39.        }else{
    40.           for($i=0,$c=count($vars);$i<$c;$i++){
    41.               unset($this->vars[$vars[$i]]);
    42.           }
    43.        }
    44.        return true;
    45.    }
    46.   // Загрушает шаблон, подставляет переменные
    47.   // Может быть использован для вставки шаблона в шаблон
    48.   // как-то так < ? echo $this->LoadData("sub_test.tpl"); ? >
    49.    function LoadData($tplname){
    50.        static $cache;
    51.        if(!$cache)$cache=array();
    52.        $md=md5($tplname);
    53.        if(!$cache[$md]){
    54.           $tplname=$this->GetTplDir($tplname);
    55.           if(is_file($tplname)){
    56.              $fp=@file_get_contents($tplname) or $this->Error("Невозможно загрузить $tplname",__METHOD__,__CLASS__,__LINE__);
    57.              if(get_magic_quotes_gpc())$fp=stripslashes($fp);
    58.              $cache[$md]=$fp;
    59.           }else $this->Error("$tplname не является файлом или не существует",__METHOD__,__CLASS__,__LINE__);
    60.        }
    61.        return $this->EvalData($cache[$md]);
    62.    }
    63.    function setErrorHandler($name){
    64.        if(function_exists($name)){
    65.            $this->errorH=$name;
    66.        }else{
    67.            $this->Error("Функция ($name) не существует",__METHOD__,__CLASS__,__LINE__);
    68.        }
    69.        return false;
    70.    }
    71.    function setCacheHandler($name){
    72.        if(function_exists($name)){
    73.            $this->cacheH=$name;
    74.        }else{
    75.            $this->Error("Функция ($name) не существует",__METHOD__,__CLASS__,__LINE__);
    76.        }
    77.        return false;
    78.    }
    79.    private function GetTplDir($tplname){
    80.        return $_SERVER["DOCUMENT_ROOT"].'/'.$this->cnf['tpl_dir'].'/'.$this->cnf['tpl_skin'].'/'.$tplname;
    81.    }
    82.    // Подставляет переменные
    83.    private function EvalData($__data){
    84.        if($this->vars['__data'])unset($this->vars['__data']);
    85.        @ob_start();
    86.            extract($this->vars);
    87.            $_GLOBALS=self::$globals; // =) чтобы не писать self::$globals
    88.            eval("?>".$__data."<?");
    89.        $res=ob_get_contents();
    90.        ob_end_clean();
    91.        return $res;
    92.    }
    93.    private function Error($msg,$m,$c,$l){
    94.        $info=array("msg"=>$msg,"method"=>$m,"class"=>$c,"line"=>$l);
    95.        eval($this->errorH.'($msg,$info);');
    96.    }
    97.    private static function _DefErrorHandler($msg,$info){
    98.        trigger_error("$msg <br> Ошибка в классе {$info['class']} на строке {$info['line']}",E_USER_ERROR);
    99.    }
    100.    private function CheckCacherSave($tplaname,$res){
    101.        if(function_exists($this->cacheH)){
    102.           $hash=md5($this->cnf['tpl_skin'].$tplname);
    103.           eval($this->cacheH.'($hash,$res);');
    104.        }
    105.        return false;
    106.    }
    107.    private function CheckCacherLoad($tplname){
    108.        if(function_exists($this->cacheH)){
    109.           $hash=md5(($this->cnf['tpl_skin'].$tplname);
    110.           eval('$res='.$this->cacheH.'($hash,false);');
    111.           return $res;
    112.        }
    113.        return false;
    114.    }
    115. }
    116.  
    117. ?>
     
  14. armadillo

    armadillo Активный пользователь

    С нами с:
    6 апр 2007
    Сообщения:
    2.380
    Симпатии:
    0
    Адрес:
    Russia, Moscow
    целесообразно сделать совместимым с PEAR - IT.php
     
  15. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Медбраты! Зовите медбратов в белых халатах! У нас здесь больные! Подать белую карету!
     
  16. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    чего это? :p
     
  17. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    Psih
    этож нативный, а ты уже нервничаешь =))
    upd. Аа ты не об этом... :D
     
  18. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Всё, вы меня достали своими шаблонизаторами. Вот вам тру код:

    PHP:
    1. <?php
    2.  
    3. /*
    4. * Templates parser class
    5. * @copyright [email=Psih@php.ru]Psih@php.ru[/email] [email=Luge@php.ru]Luge@php.ru[/email]
    6. */
    7. class Parser{
    8.  
    9.     /**
    10.      * Template name array.
    11.      *
    12.      * @var array
    13.      */
    14.     private $_template;
    15.  
    16.     /**
    17.      * Variable array
    18.      *
    19.      * @var array
    20.      */
    21.     private $_variables;
    22.  
    23.     /**
    24.      * Global variable array
    25.      *
    26.      * @var array
    27.      */
    28.     private $_global;
    29.  
    30.    /**
    31.      * Constructor
    32.      *
    33.      * @return void
    34.      */
    35.     public function __construct()
    36.     {
    37.         $this->_template = array();
    38.         $this->_variables = array();
    39.         $this->_global = array();
    40.     }
    41.  
    42.     /**
    43.      * Set template variable
    44.      *
    45.      * @param string $var_name Name, under witch data will be stored in internal associative array
    46.      * @param mixed $data
    47.      * @param bool $global If is set to "true", then global variable is created. This means it doesn't get deleted when fetched.
    48.      */
    49.     public function set($var_name, $data, $global = false)
    50.     {
    51.         $this->{$global ? '_global' : '_variables'}[$var_name] = $data;
    52.     }
    53.  
    54.     /**
    55.      * Load template by include and return processed string.
    56.      * If file wasn't found - give a fatal error.
    57.      *
    58.      * @param string $file
    59.      * @return string
    60.      */
    61.     public function load($file)
    62.     {
    63.         if (file_exists($file)) {
    64.             ob_start();
    65.             include($file);
    66.             return ob_get_clean();
    67.         }else {
    68.             trigger_error('File '.$file.' doesn\'t exist', E_USER_ERROR);
    69.         }
    70.     }
    71.  
    72.     /**
    73.      * Appends data to existing variable. If variable doesn't exists - creates it.
    74.      * Works only with string or int type data.
    75.      *
    76.      * @param string $var_name Name, under witch data will be stored in internal associative array
    77.      * @param string $data
    78.      * @param bool $global If is set to "true", then global variable is created. This means it doesn't get deleted when fetched.
    79.      * @return void
    80.      */
    81.     public function append($var_name, $data, $global = false){
    82.  
    83.         $arr = $global ? '_global' : '_variables';
    84.         if (isset($this->{$arr}[$var_name])){
    85.             $this->{$arr}[$var_name] .= $data;
    86.         } else {
    87.             $this->{$arr}[$var_name] = $data;
    88.         }
    89.     }
    90.  
    91.     /**
    92.      * Get stored data by key name. If data isn't global, it get's deleted after first access.
    93.      * Global variables never get deleted
    94.      *
    95.      * @param string $var_name
    96.      * @param bool $global
    97.      * @return mixed
    98.      */
    99.     public function get($var_name, $global = false)
    100.     {
    101.         if ($global && isset($this->_global[$var_name])) {
    102.             return $this->_global[$var_name];
    103.         } elseif (isset($this->_variables[$var_name])) {
    104.             $var = $this->_variables[$var_name];
    105.             unset($this->_variables[$var_name]);
    106.             return $var;
    107.         }
    108.         return null;
    109.     }
    110.  
    111.     /**
    112.      * Check if variable with such key exists
    113.      *
    114.      * @param string $var_name
    115.      * @param bool $global
    116.      * @return bool
    117.      */
    118.     public function is_set($var_name, $global = false)
    119.     {
    120.         return (!empty($this->{$global ? '_global' : '_variables'}[$var_name])) ? true : false;
    121.     }
    122. }
     
  19. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    вы чё вдвоём писали? 0о
     
  20. kostyl

    kostyl Guest

    они ж на пару работают...
    Psih
    Че то не понял логику работы. Как переменные превращаются в переменные шаблона? У меня например происходит extract...
     
  21. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    kostyl
    у него <?=$this->get("var");?>
     
  22. kostyl

    kostyl Guest

    Psih
    а чем экстракт плох?
     
  23. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    наверно тем, что он мусорит в глобальной области видимости.
     
  24. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    EXTR_PREFIX_SAME
     
  25. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Mr.M.I.T.
    kostyl
    <?php echo get('var_name')?> в шаблоне, для этого в библиотеке функций есть следующее:
    PHP:
    1. <?php
    2. /* short parser functions */
    3. function get($name,$global=false)
    4. {
    5.     return Core::getInstance('parser',true)->get($name,$global);
    6. }
    7.  
    8. function is_set($name,$global=false)
    9. {
    10.     return Core::getInstance('parser',true)->is_set($name,$global);
    11. }
    Экстракт не плох,я просто его не юзаю:
    1). Он действительно мусорит в глобальной области видимости => более долгий lookup, нету гарантии что не пересекутся имена переменных.
    2). Не позволяет сделать супер-глобальную переменную для шаблонов (это наверно главный минус).
    3). У меня данные при фетче тут-же чистятся по умолчанию, что снижает memory usage.

    А вообще тут устройство самого движка и философия KISS тесно связаны, когда можно пощупать весь движок целиком становится понятно что к чему. Luge вроде нравится, я даже не слышал ни разу предложений что-то сильно менять :)