22 ноября 2008 в 17:53

Нативный шаблонизатор

PHP*
Я довольно давно уже использую нативные шаблоны, но, почему-то, у многих людей нативные шаблоны ассоциируются с конструкциями типа:

  1. $title = 'My title';
  2. include('templates/index.html');
* This source code was highlighted with Source Code Highlighter.

  1. <html><head><title><?php echo $title ?></title></head>
  2. <!-- ... -->
* This source code was highlighted with Source Code Highlighter.


То есть, переменную определили и приинклюдили html-файл. Я считаю, что это в корне неверный подход. Почему?

Во-первых, все переменные, переданные в шаблон, должны храниться в одном месте (свойстве класса шаблонизатора).
Во-вторых, в шаблонизаторе не должно быть доступа к переменным, которые в него не переданы, и к функциям, которые в нем не определены.
В-третьих, должен быть определен набор функций, необходимых для работы.

Таким образом, я пришел к выводу, что шаблонизатор нужен, но он не должен быть навороченным тормозом типа Smarty.
Идеология блочных шаблонизаторов (XTemplate, например) мне не импонирует потому, что в них нет ветвлений как таковых, есть только циклы.

Потому я написал свой.

Начнем с того, что нам нужно разобраться с обработкой ошибок. Я использую для этой цели исключения, потому определяем класс исключений:

  1. class STempException extends Exception {}
* This source code was highlighted with Source Code Highlighter.

Тут нам больше ничего не нужно. Переходим к самому шаблонизатору (о том, что делают методы, кратко написано в комментариях, более подробно опишу ниже):

  1. class STemp
  2. {
  3.   /**
  4.    * The name of the directory where templates are located.
  5.    *
  6.    * @var string.
  7.    * @access private.
  8.    */
  9.   private $path;
  10.   
  11.   /**
  12.    * Name of the template.
  13.    *
  14.    * @var string.
  15.    * @access private.
  16.    */
  17.   private $template;
  18.   
  19.   /**
  20.    * Where assigned template vars are kept.
  21.    *
  22.    * @var array.
  23.    * @access private.
  24.    */
  25.   private $variables = array();
  26.   
  27.   /**
  28.    * Parameters of the template engine.
  29.    *
  30.    * @var array.
  31.    * @access private
  32.    */
  33.   private $params = array(
  34.     'xss_protection' => true,
  35.     'exit_after_display' => true,
  36.     'endofline_to_br' => false
  37.     );
  38.   
  39.   /**
  40.    * File that include in template.
  41.    *
  42.    * @var string.
  43.    * @access private.
  44.    */
  45.   private $include_file;
  46.   
  47.   /**
  48.    * The class constructor. Set name of the directory where templates are located.
  49.    *
  50.    * @param string $path name of the directory where templates are located, default 'templates/'.
  51.    * @access public.
  52.    */
  53.   public function __construct($path = 'templates/')
  54.   {
  55.     $this->path = $path;
  56.   }
  57.   
  58.   /**
  59.    * Set parameters of template engine.
  60.    *
  61.    * @param string $param name of the parameter.
  62.    * @param bool $value value of the parameter.
  63.    * @return bool TRUE if parameter set, FALSE if didn't set.
  64.    * @access public.
  65.    */
  66.   public function setParam($param, $value)
  67.   {
  68.     if (isset($this->params[$param])) {
  69.       $this->params[$param] = $value;
  70.       return true;
  71.     }
  72.       
  73.     return false;
  74.   }
  75.   
  76.   /**
  77.    *
  78.    * @param string $include_file path to include file.
  79.    * @access public.
  80.    */
  81.   public function setIncludeFile($include_file)
  82.   {          
  83.     $this->include_file = $this->path.$include_file;
  84.     
  85.     if (!file_exists($this->path.$include_file))
  86.       throw new STempException('Include file '.$this->include_file.' not exitst');
  87.   }
  88.   
  89.   /**
  90.    * Assigns values to template variables.
  91.    *
  92.    * @param string $name the template variable name.
  93.    * @param mixed $value the value to assign.
  94.    * @access public.
  95.    */
  96.   public function assign($name, $value)
  97.   {
  98.     $this->variables[$name] = $value;
  99.   }
  100.   
  101.   /**
  102.    * Executes and displays the template results.
  103.    *
  104.    * @param string $template the template name.
  105.    * @access public.
  106.    */
  107.   public function display($template)
  108.   {
  109.     $this->template = $this->path.$template;
  110.     
  111.     if (!file_exists($this->template))
  112.       throw new STempException('Template file '.$template.' not exitst');
  113.       
  114.     require_once($this->template);
  115.     
  116.     if ($this->params['exit_after_display'])
  117.       exit;
  118.   }
  119.   
  120.   /**
  121.    * Get value of template variable.
  122.    *
  123.    * @param string $name the template variable name.
  124.    * @return mixed value of template variable with this name. FALSE if variable not set.
  125.    * @access private.
  126.    */
  127.   private function __get($name)
  128.   {
  129.     if (isset($this->variables[$name])) {
  130.       $variable = $this->variables[$name];
  131.       
  132.       if ($this->params['xss_protection'])
  133.         $variable = $this->xssProtection($variable);
  134.         
  135.       if ($this->params['endofline_to_br'])
  136.         $variable = $this->endoflineToBr($variable);
  137.         
  138.       return $variable;
  139.     }
  140.     
  141.     return NULL;
  142.   }
  143.   
  144.   /**
  145.    * Include file
  146.    *
  147.    * @access private
  148.    */
  149.   private function includeFile()
  150.   {
  151.     if (!file_exists($this->include_file))
  152.       throw new STempException('Include file '.$this->include_file.' not found');
  153.       
  154.     require_once($this->include_file);
  155.   }
  156.   
  157.   /**
  158.    * For the formation of endings of words.
  159.    *
  160.    * @param int $value number.
  161.    * @param string $word0 word in the singular.
  162.    * @param string $word1 word in the plural (2, 3).
  163.    * @param string $word2 word in the plural.
  164.    * @param string $separator separator, default ' '.
  165.    * @return string formed words
  166.    * @access private.
  167.    */
  168.   private function morph($value, $word0, $word1, $word2, $separator = ' ')
  169.   {
  170.     if (preg_match('/1\d$/', $value))
  171.       return $value.$separator.$word2;
  172.     elseif (preg_match('/1$/', $value))
  173.       return $value.$separator.$word0;
  174.     elseif (preg_match('/(2|3|4)$/', $value))
  175.       return $value.$separator.$word1;
  176.     else
  177.       return $value.$separator.$word2;
  178.   }
  179.   
  180.   /**
  181.    * For protection from XSS.
  182.    *
  183.    * @param mixed $variable data for protection.
  184.    * @return mixed protected data.
  185.    * @access private.
  186.    */
  187.   private function xssProtection($variable)
  188.   {
  189.     if (is_array($variable)) {
  190.       $protected = array();
  191.       foreach ($variable as $key=>$value)
  192.         $protected[$key] = $this->xssProtection($value);
  193.       return $protected;
  194.     }
  195.     
  196.     return htmlspecialchars($variable);
  197.   }
  198.   
  199.   /**
  200.    * Inserts HTML line breaks before all newlines in a string.
  201.    *
  202.    * @param mixed $variable data for protection.
  203.    * @return mixed data where string with <br /> inserted before all newlines.
  204.    * @access private.
  205.    */
  206.   private function endoflineToBr($variable)
  207.   {
  208.     if (is_array($variable)) {
  209.       $protected = array();
  210.       foreach ($variable as $key=>$value)
  211.         $protected[$key] = $this->endoflineToBr($value);
  212.       return $protected;
  213.     }
  214.     
  215.     return nl2br($variable);
  216.   }
  217. }
* This source code was highlighted with Source Code Highlighter.


В конструкторе мы можем указать путь к директории шаблонов (по умолчанию temlates/).

С помощью метода setParam мы можем установить параметры шаблонизатора. Их всего три (мне этого достаточно, при необходимости можно добавлять параметры). Первый параметр — xss_protection — как понятно из названия, нужен для защиты от уязвимости xss. Если значение параметра установлено как true, все переменные, которые мы используем в шаблоне, перед отдачей автоматически обрабатываются функцией htmlspecialchars (в том числе элементы массивов). Второй параметр — exit_after_display — нужен для того, чтобы, при потребности, мы могли остановить выполнение сценария после отображения шаблона. Третий параметр — endofline_to_br — обрабатывает все переменные перед отдачей (в том числе элементы массивов) функцией nl2br.

Методом setIncludeFile мы можем установить подключаемый шаблон. Очень часто используется общий шаблон index.tpl.php и в него, в зависимости от условий, подключают изменямую часть. Вот для автоматизации данного процесса и нужен этот метод. Если подключаемый файл не существует, выбрасыватся исключение.

Метод assign служит для передачи переменных в шаблон.

Метод display отображает шаблон. Если файл шаблона не существует, выбрасывается исключение. Если параметр exit_after_display установлен как true, этот метод также завершает работу сценария (практически всегда отображение шаблона является последним действием).

«Магический» метод __get возвращает значение переданной в шаблон переменной. Если переменная не определена, возвращает NULL. В зависимости от параметров, переменные перед отдачей могут обрабатываться.

Метод includeFile инклюдит файл, назначенный методом setIncludeFile и выбрасывает исключение, если этот файл не найден.

Метод morph, не совсем «шаблонизаторный», служит для формирования правильных окончание слов, относящихся к числительным. То есть, 1 комментарий, 2 комментария, 5 комментариев. В метод нужно передать само число, три разных варианта и, опционально, разделитель слов (по умолчанию неразрывный пробел).

Метод xssProtection обрабатывает данные функцией htmlspecialchars. Если на входе массив, то он рекурсивно перебирается и обрабатываются все его элементы.

Метод endoflineToBr обрабатывает данные функцией nl2br. Если на входе массив, то он, как и в предыдущем методе, рекурсивно перебирается и обрабатываются все его элементы.

Как это выглядит на практике? Предположим, нам нужно распечатать статью и комментарии к ней. Данные по статье в массиве $article, комменты — в $comments.

Контроллер:
  1. $stemp = new STemp();
  2.  
  3. $stemp->assign("title", $article['title']);
  4.  
  5. $stemp->assign("article", $article);
  6. $stemp->assign("comments", $comments);
  7.  
  8. try {
  9.   $stemp->setIncludeFile("article.tpl.php");
  10.   $stemp->display("index.tpl.php");
  11. } catch (STempException $e) {
  12.   die('STemp error: '.$e->getMessage());
  13. }
* This source code was highlighted with Source Code Highlighter.


Шаблон index.tpl.php:
  1. <html>
  2. <head>
  3. <title><?php echo $this->title ?></title>
  4. </head>
  5. <body>
  6. <?php $this->includeFile() ?>
  7. </body>
  8. </html>
* This source code was highlighted with Source Code Highlighter.


Шаблон article.tpl.php:
  1. <h1><?php echo $this->article['title'] ?></h1>
  2. <?php $this->setParam('xss_protection', false); $this->setParam('endofline_to_br', true) ?>
  3. <div class="content">
  4. <?php echo $this->article['content'] ?>
  5. </div>
  6. <p><?php echo $this->morph(count($this->comments), 'комментарий', 'комментария', 'комментариев') ?>:</p>
  7. <?php $this->setParam('xss_protecttion', true) ?>
  8. <?php foreach ($this->comments as $key=>$value) { ?>
  9. <p class="user"><?php echo $value['username'] ?>:</p>
  10. <p class="comment"><?php echo $value['text'] ?></p>
  11. <?php } ?>
* This source code was highlighted with Source Code Highlighter.


Скачать класс.

Использование класса в личных нуждах разрешено без ограничений. При перепечатке статьи или исходного кода, в том числе, частично, ссылка на меня (на мой сайт) обязательна.
Андрей Сабинин @s_a_p
карма
48,6
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (86)

  • +4
    Тоже пользовался нативными само писными шаблонизаторами, поэтому позволю себе написать критику.

    первое # <?php echo $this->title ?>
    такой вызов слишком долг, и не естественнен, если вы будите выводить шаблон в метоlе, то сможете в этом же методе сделать что-то вроде:
    foreach $this->allVars as $k=>$v
    $$k=$v;
    ну фильтры для протекции навернете сами.

    тогда запись будет более естественой:
    <?=$title ?>

    второе: я считаю что экранирование передаваемых в шаблонизатор параметров должно определяться на момент передачи параметра и по умолчанию все экранировать:
    $templater->assign($varName,$value, $isProtect=true)

    Это что касается критики, а в остальном статья толковая, очень правильно подмечены причины почему нужен шаблонизатор, а не «чистый» нативный подход. Для начинающих однозначно полезный материал!
    • +1
      1. К сожалению, не везде short_open_tag == 1, поэтому, если нужно, чтобы сайт работал на любом хостинге, приходится прибегать к длинным вызовам. И, я думаю, не нужно слишком усложнять нативный шаблонизатор, а то в конце концов получим тот же Smarty :)

      2. Не думаю. Вдруг в одном месте нужно вывести экранированные данные, а в другом чистые?

      В любом случае, спасибо за критику и за отзыв :)
      • +1
        очевидно вы не поняли что имеется в момем первом положении. Суть в том чтоб избавиться от <?php echo $this->title
        Нужное выделил
        а не привести к коротким тегам, это уж дело вкуса.

        второе:
        как правило когда нужно выводить «чистые» данные вы знаете это в моделе (практика подтверждает)
        и в конце концов вы всегда можете использовать оба механизма.
        • 0
          Не замедлят ли фильтры работу при большом количестве переменных? Одним из моих аргументов в пользу нативных шаблонов всегда была их скорость работы.
          • 0
            копирование даже сотни переменных — очень быстрая операция, а про экранирование на этапе добавления, я делал так:
            assign($varName,$value, $isProtect=true){
            if ($isProtect)
            $value = escape($value);
            }

            Вообще в этих вопросах скорости работает такое правило: смарти (несмотря на всю свою кривизну, громоздкость) популярен потому что на вывод шаблона тратится 2-10% времени редко больше.
        • +1
          Угу. Именно так и сделано у меня во фреймворке, упоминание которого в блоге так раскритиковали :)

          Собственно, вот весь код рендерера: hg.balancer.ru/hgwebdir/bors-core/file/6a98c39d7867/classes/render/php.php

          А вот — пример шаблона: hg.balancer.ru/hgwebdir/bors-airbase/file/30143ac88174/classes/bors/airbase/board/show/latest.tpl.php

          Класс-источник данных для шаблона: hg.balancer.ru/hgwebdir/bors-airbase/file/30143ac88174/classes/bors/airbase/board/show/latest.php

          И, собственно, результат работы: forums.airbase.ru/forum/latest/

          (хотя фишка пока чисто экспериментальная, всё остальное — на Smarty)
          • 0
            Кстати!!! килограмм Кармы тому кто придумает избавиться от
            ob_start();
            $result = ob_get_contents();
            Проблема в том, что функции перехвата потока вывода сильно медленно работают!

            посмотрел ваш фраймворк и наскидку вижу что логика очень прозрачная — это плюс, но много сущностей плодите — это минус, простите если ошибся.
            • 0
              >Кстати!!! килограмм Кармы тому кто придумает избавиться от
              ob_start();

              Простого (и более быстрого, чем с перехватом) способа, ИМХО, не будет. Можно запретить в шаблоне любой вывод, сразу начинать его с "<?php..." и отказаться от всех echo в пользу $RESULT.="..."

              :)

              Кроме того, в моём случае можно написать обработчик не уровня рендерера тела страницы, а уровня генератора страницы в целом. Но тогда проще уже сделать шаг дальше и тупо писать отдельный PHP-скрипт, который и будет выводить всё сам (пусть и пользуясь для извлечения данных функционалом фреймворка).

              >наскидку вижу что логика очень прозрачная

              Это было одной из целей.

              >это плюс, но много сущностей плодите

              Есть такое, это обратная сторона микроядерности, модульности и многофункциональности. Компенсируется тем, что сущности все простые и унифицированные :)

              Фактически, этот фреймворк — не столько набор кода, сколько набор правил и соглашений. Поэтому, когда придёт надобность, смогу быстро реализовать его на любом языке с развитой рефлексией (в ближайших планах Java, но не в области Web'а).
              • 0
                поподробней подалуйста что вам не нравится в механизмах рефлексии PHP!
                читать если нужно напишу подробней, просто спроса не было.

                >>Простого (и более быстрого, чем с перехватом) способа, ИМХО, не будет. Можно запретить в шаблоне любой вывод, сразу начинать его с "<?php..." и отказаться от всех echo в пользу $RESULT.="..."

                вот что-то такое я как-то думал сделать с прекомпиляцией, но слишком много нюансов =)
                • 0
                  >поподробней подалуйста что вам не нравится в механизмах рефлексии PHP!

                  Всё нравится :) Я говорю, что мою идеологию можно реализовать на любом языке с развитой рефлексией. Сейчас использую PHP. Столь же легко сделать на Python или Ruby, Perl. Немного подумав, можно и на Java :) Чем и планирую заняться как будет время и желание…

                  >читать если нужно напишу подробней

                  Пробежал пока очень бегло. У меня используется что-то среднее… Пример GET/POST обработчика actions: hg.balancer.ru/hgwebdir/bors-core/file/6a98c39d7867/inc/bors/form_save.php (19-я, 139-я строки).

                  Кстати, небольшой хинт: if(substr($str,0,1)=='_') работает _намного_ медленнее, чем if(preg_match('/^_/', $str)) :) Видимо, вся фишка в экономии на создании временной строки. Я когда-то Smarty так ускорил раз в 5, подменив массу substr или {} на соответствующие preg_*, особенно, когда после сравнения требовалось ещё и подстроки извлекать. Правда, было это во времена ~2.6.14. Патчи мои не приняли без комментариев, повторно уже не связывался :)
                  • 0
                    Хинт принял, проверю, учту. Спасибо
                  • +3
                    Немножко не в тему, но почему-то мне кажется, что if ($str[0]=="_") будет еще быстрее.

                    Кстати, проверил сейчас, оказалось, что
                    if(substr($str,0,1)=='_') работает 1,2 времени, if($str[0]=='_') — 0,7 времени, а if(preg_match('/^_/', $str)) — 2,1.
                    • 0
                      Да, похоже в PHP за последнее время что-то соптимизировали. У меня сейчас так вышло:

                      substr: time=1.13405108452
                      preg_match: time=1.37561202049
                      array: time=0.460075139999
                    • 0
                      Зато немного интереснее проверка последнего символа:

                      substr: time=1.58525490761
                      preg_match: time=1.44538593292
                      array: time=1.14502310753

                      И, наконец, цепочки символов (задача — проверить расширение файла):

                      substr: time=1.64853310585
                      preg_match: time=1.44550204277

                      (массив тут уже непригоден).
                      • 0
                        расширение файла: habrahabr.ru/blogs/php/37753/
                        • 0
                          Проверить и получить — немного разные вещи :)

                          Вот сравнение самого быстрого метода со ссылки и preg_match:

                          test_strrchr: 3.47257399559
                          test_preg_match: 1.57902884483

                          Почувствуйте разницу :)

                          Если же речь идёт просто об извлечении, то:

                          test_strrchr: 3.38620686531
                          test_preg_match: 2.8885819912

                          Даже чтобы извлечь последние символы после ".", preg_match работает быстрее :) Кроме того, он отловит и отсутствие расширения…
                          • 0
                            А что, если strrpos($str, $ext) != strlen($str) — strlen($ext)?
                            Плюс можно еще offset вставить в $strrpos, с ним наверняка еще ускорить можно.
                    • 0
                      , if($str[0]=='_')

                      тогда уж

                      if($str{0}=='_')

                      пишите правильно, господа!
                      • 0
                        В чем разница?
                        Сейчас потестировал для разных типов: array, string, int; разницы в поведении не заметил.

                        Что я не углядел?
                        • 0
                          в том что второй вариант специально для работы с строками в 6-й версии обещали ошибку сделать на неправильные вызовы
                          • 0
                            Думаю, разница в будущем может вылезти на мультибайтовых строках.

                            Сейчас, правда, ни тот, ни другой формат записи с таковыми не работают.
                          • 0
                            Я конечно же могу ошибать, но вроде как история такая:
                            до PHP4 использовали [], потом стали рекомендовать {}, а к 6-ке собираются опять вернуться к []
                            • 0
                              Хорошо бы :)

                              Не вижу ничего плохого, чтобы обращаться к строке как к массиву символов!
      • 0
        А ini_set('short_open_tag', 'On'); перед инклудом не сработает? (дома не могу проверить)
        • 0
          да да проверьте и расскажите мне, очень интересно.
          • 0
            В документации:

            short_open_tag || «1» || PHP_INI_PERDIR || PHP_INI_ALL в PHP <= 4.0.0.

            Т.е. сработает в 3.xx или 4.0.0 только :)
          • 0
            Цитата из мануала:
            short_open_tag
            PHP_INI_PERDIR
            PHP_INI_ALL in PHP <= 4.0.0.

            PHP_INI_PERDIR 2 Entry can be set in php.ini, .htaccess or httpd.conf

            Так что в .htaccess прописать использование коротких тегов — можно, но во время исполнения скрипта — нельзя :(
    • +4
      И я, и я пользовался нативными шаблонизаторами :) потому позвольте чуть-чуть поправить ваше предложение, вместо foreach($this->allVars as $k => $v) { $$k = $v; } есть языковая конструкция extract.
      • 0
        вы правы, вы абсолютно правы, мое упущение =)
    • 0
      Нет, по моему лучше определять экранирование в шаблоне, примерно так:

      → с экранированием: {$var}

      → без: {useHTML:$var}

      Все-таки это логичнее, так как передача массива из HTML- и non-HTML данных в вашем примере невозможна.
  • 0
    «Магический» метод __get возвращает значение переданной в шаблон переменной. Если переменная не определена, возвращает false. В зависимости от параметров, переменные перед отдачей могут обрабатываться.


    в большенстве случаев этого хватает, но на всякий случай замечу, что переменная может быть определена и иметь false, потому полезно генерировать стандартное уведомление.
    а вообще функции для работы с буферами часто пишут так что они возвращают null:
    fucntion __get($k){
         if (isset($this->vars[$k]))
              return $this->vars[$k];
        return null;
    }
    
    • 0
      Я вот, кстати, довольно долго думал, возвращать false или null (ведь переменная можеть быть определена и иметь значение как false, так и null). Решил, что false логичнее. Вы считаете, что null логичнее?
      • +1
        я б null возвращал, ибо попытка вывода несуществующей переменной — колокольчик о том, что там ошибка в логике (ошибочное предположение существования), опечатка, и вы так получите нотис!
        • 0
          Думаю, вы правы. Спасибо, сейчас изменю.
        • 0
          смотрим документацию и коментарии и видим, что присвоение переменной null эквивалентно уничтожению переменной (фактически данных нет, но есть контейнер).
    • 0
      Может логичнее возвращать «undefined»?
      • 0
        вплане ошибку?
      • 0
        Не совсем вас понял. Уточните, пожалуйста.
        • –3
          <?php echo $this->varName; ?>
          Если переменная «varName» не установлена, то метод __get () вернет строку «undefined».
          • +2
            Тогда придется в конструкциях типа
            <?php if (!$this->user) { ?><a href="/authorization">Авторизуйтесь</a><?php } ?>
            писать
            <?php if ($this->user == "undefined") { ?><a href="/authorization">Авторизуйтесь</a><?php } ?>
            Мало того, что неудобно, еще и нарушает логику, имхо.
            • 0
              Если Вы обращаетесь в переменной user, зная, что она может быть не объявлена, это еще хуже…
              • +2
                нотисы генерируйте =) а не заглушки данных.
            • 0
              Это извращение, точнее ошибка архитектуры.
              Нужно на уровне подготовки данных, «включать» и «выключать» модули (в зависимости от ролей и доступа)
              Потом просто указваете… ну например <?php block('auth'); ?> или кто любит ООП <?php $interface->assign('block', 'auth');
              $interface->view(); ?> и т.п. В зависимости от поступленных данных и развивается сценарий ;)
              Т.е. если нет доступа юзеру, то модуль авторизации не должен включаться или выводить catch шаблона на выход.
          • +1
            А если переменная будет содержать такое значение? Самый правильный вариант всё таки с null.
          • +1
            это echo вам выведет «undefined»
        • 0
          думаю имеется ввиду следующее:

          если воспроизвести такой код:
          <?
          error_reporting(E_ALL);
          echo $a;
          $a=null;
          echo $a;
          ?>

          то получим только один Notice, тоесть я ошибся выше и скрипт не будет вам кидать нотис на строке $this- > undefined; кидать такой нотис нужно самому используя trigger_error, определяя место вызова из debug_backtrace();

          Знаете, кстати, если б вы инклюдили шаблон внутри метода, предварительно сделав:
          что-то вроде:
          foreach $this->allVars as $k=>$v
          $$k=$v;
          как я писал выше. то получили бы и нотис автоматически на строке <?php echo $title ?>
          если переменная не зарегистрирована.
      • 0
        супер решение, можно еще так же возвращать что-то вроде «nothing», «void», а но лучше всего, мы же русские люди, возвращать строку «не установлено», ну или в крайнем случаее «ничего нет».

        Здесь вам не явоскрипт, где undefined — конструкция языка такая же как и null
        • –1
          нет в яваскрипте никакой конструкции unefined.
          • 0
            Да? А вот это наверное уличная магия:
            var foo;
            if (foo == undefined)
            {
            alert(«foo is undefined»);
            }

            Угадай с трех раз, появица ли у нас алерт или нет.
            • 0
              alert( 'undefined' in window );

              javascript.ru/ecmascript/part15#a-15.1
  • +2
    > навороченным тормозом типа Smarty.
    вы делали замеры и сравнение со своим шаблонизатором? попробуйте, будет интересно.
    • +3
      чтоб сделать замер подобного толка нужно написать сложный шаблон для смарти и посмотреть.
      из своей практики могу сказать результаты тестирования:
      Простые шаблоны

      генерация шаблонов:
      Q AVG: 0.16 сек
      S AVG: 0.18 сек
      P AVG: — сек

      вывод шаблонов:
      Q AVG: 0.0064 сек
      S AVG: 0.0065 сек
      P AVG: 0.0022 сек

      Сложные шаблоны

      генерация шаблонов:
      Q AVG: 0.19 сек
      S AVG: 0.54 сек
      P AVG: — сек

      вывод шаблонов:
      Q AVG: 0.0072 сек
      S AVG: 0.0231 сек
      P AVG: 0.0038 сек

      AVG — среднее время на замере (1000 замеров)
      Тестировалось на рабочем сервере FReeBSD Core2Duo 2.0 Ghz, думаю остальные параметры не так важны, да и не помню я их уже.

      генерация шаблонов — имеется ввиду компилирование.
      S- smarty
      Q- Quiky
      P — PHP
    • 0
      Да, притом не на той верстке, которая в примере, а на реальной и сложной верстке. Smarty медленней. Кроме того, нативные шаблоны более гибкие.

      Но! Я не утверждаю, что Smarty… скажем так, плохой. Если кому-то удобен Smarty, и с его помощью он легко и быстро решает нужные задачи — это хорошо :) А мне вот удобнее так, кроме того, в Smarty мне действительно не хватает гибкости.
      • 0
        приведу пример:
        Колоночная верстка, выводится 2 или 3 колонки каких-то параметров в зависимости от их количества. Провести базовые арифметические процедуры в смарти очень не удобно! а на нативном коде делаются очень просто. Эта проблема решена в квики (Quiky)
      • +1
        Я все же решил проверить на этом примере :)
        Smarty — 0.0119
        Stemp — 0.0026

        Про гибкость согласен, возможностей для работы с объектами/массивами мало, вся надежда на смарти 3 :)

        А код Stemp понравился, буду рекомендовать тем, кому нужен php-native шаблонизатор
  • +3
    Есть ещё проблема с самописными шаблонизаторами — они не подходят для коллективной разработки, поскольку в коллективной разработке нужно использовать хорошо-документированные технологии, проверенные временем, иначе при ротации сотрудников, у вас возникает проблема.
    • –4
      с вами не согласен в корне.
      технология php проверена и обкатана, а изучить синтаксис PHP на уровне вывода переменных не труднее чем выучить синтаксис Smarty
      • +1
        Очень хорошо, теперь ответьте мне на вопрос:
        Учавствовали ли вы в больших проектах (4-5 человек) длительное время (более года). Если да, то менялись ли у вас разработчики (хотябы 1, 2 в год). Если да, то использовали ли вы там альтернативные самописные решения. Если да, то не возникало ли ни у кого никаких проблем с этим?

        Лично я учавствую в проекте более 3 лет, на 6 разработчиков, 25% из них поменялалсь 2 раза. И у нас постоянно возникают проблемы с простейшей вещью — недокументированным модулем к Drupal, который содержит альтернативный функционал модулю node.
        • +2
          Учавствовали ли вы в больших проектах (4-5 человек) длительное время (более года)
          — да (последний оконченный, ну с которого я ушел рекламно ресселерская сеть, с нагрузкой 600хитов(просмотров) в секунду, должность тимлидер, 7 человек команда).
          Если да, то менялись ли у вас разработчики (хотябы 1, 2 в год).
          — да (менялось 4 человека, 3 ушло 4 пришло за год)
          Если да, то использовали ли вы там альтернативные самописные решения.
          — Да, уровень абстракции доступа к БД, RPC модули
          Если да, то не возникало ли ни у кого никаких проблем с этим?
          — Да возникли, но мы работал и в системе экстремальное программирования, в частности программирование в парах и человек который не смог разобраться оказался не коммуникабельным и ушел из проекта, остальные 3 остались.

          в итоге с проекта ушли фактически все, кто занимался им в начале (остался один!, остальные с новой командой). Проект успешен, работает и расширяется.

          я считаю что боязнь перед использованием собственных разработок в коллективе прежде всего из за плохой коммуникации. Улучшайте среду общения и будет лучше! что стоят 10000 строк вашей документации если у новичка есть конкретная проблема? а что если эта проблема ранее не обсуждалась? ждать нового релиза, рыться в кодах готового продукта (который часто выглядит черным ящиком)?
          в целом я ЗА использование готовых решений, но иногда это не оправдано.
          В частности не оправдан страх научить других людей пользоваться вашим решением, если решение работает, и нормальный код, то даже документация большинству не понадобится
          • +1
            Да дело в том, что боязни как раз обычно и нет, отсюда и вылазят грабли.

            >> В частности не оправдан страх научить других людей пользоваться вашим решением, если решение работает, и нормальный код, то даже документация большинству не понадобится. <<
            У нас в компании так не практикуется, если своё решение качует между проектами, то к нему есть документация (и не только АПИ).
            • 0
              все верно, видимо у вас «большая» компания, а у меня маленькая (менее 100 человек) была у нас непростительно много времени уйдет на формирование документации, проще на словах или прямо в коде описать исключения. Все эти методы привязаны к маштабу. Когда маштаб большой и нужно координировать много народу, то другие приемы работают.
    • 0
      у нас есть документация, но писать с помошью этого просто не реально
      для коллективной разработки нужны простые решения
      • 0
        вот этот аргумент мне понятен больше
  • 0
    Поддерживаю такой подход, но сам склонился в сторону XSLT.
    По поводу исключений: я считаю делать так нельзя по той причине, что отсутствие шаблона не является критической ошибкой, можно было бы просто взять за контент несуществующего шаблона пустую строку. А так, придется каждую обработку шаблона ставить в try-catch и не дай Бог это забыть…
    • +1
      спорно… что такое не существующий шаблон? отсутствие запланированного вывода. не поставите try-catch будет ошибка — логично.
    • 0
      Ну никто же не заставляет писать die(); в catch. Вы можете обработать исключение так, как вам будет нужно. Обычно, отсутствие шаблона — это все же критическая ошибка. Я в большинстве случаев при отсутствии шаблона отдаю 404.

      Кроме того, в try-catch достаточно поместить метод display.
      • –2
        Я про то, что работа скрипта завершиться, если не поставить try-catch.
        • 0
          set_exception_handler
          • +1
            set_exception_handler не спасет от завершения работы скрипта, в отличии от error_handler'а «оттуда не возвращаются» :)
  • 0
     У вас кажется опечатка в комментарии на 163 строчке. А в целом конечно хорошо — название метода XssProtection порадовало) Я бы добавил еще такую функциональность(давно крутятся просто такие идеи, а реализовывать особо не где):
     Сделать пакеты со вспомогательными функциями. Например функции для работы с датой(разные представления, рендерилки различных селекторов дат и т.д. — кстати в Smarty такое очень удобно сделано). А что бы не тащить это за собой для всех шаблонов, можно добавить параметры(аналогично xss_protection) которые будут определять нужно ли делать require_once для данного набора функции.
    • +1
      Да, опечатка. Спасибо, исправил.

      Работу с датами решил не делать, чтобы не нагружать шаблонизатор. Я не вижу смысла делать функцию, аналогичную php-функции date();

      А вообще, я буду только рад, если кто-то возьмет мой класс и доработает его для себя :) Я же старался реализовать самый минимум (на мой взгляд).
  • 0
    еще в 2005 году был сделан phpsavant.com, api совместимо со смарти, плагин работы с формами присутвует

    <?php

    // Load the Savant3 class file and create an instance.
    require_once 'Savant3.php';
    $tpl = new Savant3();

    // Create a title.
    $name = «Some Of My Favorite Books»;

    // Generate an array of book authors and titles.
    $booklist = array(
        array(
            'author' => 'Hernando de Soto',
            'title' => 'The Mystery of Capitalism'
        ),
        array(
            'author' => 'Neal Stephenson',
            'title' => 'Cryptonomicon'
        ),
        array(
            'author' => 'Milton Friedman',
            'title' => 'Free to Choose'
        )
    );

    // Assign values to the Savant instance.
    $tpl->title = $name;
    $tpl->books = $booklist;

    // Display a template using the assigned values.
    $tpl->display('books.tpl.php');
    ?>


    <html>

        <head>
            <title><?php echo $this->eprint($this->title); ?></title>
        </head>

        <body>
            
            <?php if (is_array($this->books)): ?>
                
                <!-- A table of some books. -->
                <table>
                    <tr>
                        <th>Author</th>
                        <th>Title</th>
                    </tr>
                    
                    <?php foreach ($this->books as $key => $val): ?>
                        <tr>
                            <td><?php echo $this->eprint($val['author']); ?></td>
                            <td><?php echo $this->eprint($val['title']); ?></td>
                        </tr>
                    <?php endforeach; ?>
                    
                </table>
                
            <?php else: ?>
                
                <p>There are no books to display.</p>
                
            <?php endif; ?>
            
        </body>
    </html>


    зы от __set и __get стоит иногда отказываться, тк на производительности это может сказать хорошо
  • +6
    очередной велосипед с треугольными колёсами. тысячи их!
    когда-нибудь тебе надоест, трахаться с «ветвлениями как таковыми» и ты проникнешься идеей «блочных шаблонизаторов».
    когда-нибудь ты поймёшь, что ооп — это прежде всего объектная декомпозиция, а не запихивание всех процедурок в один класс.
    когда-нибудь ты возненавидешь, спагетти и начнёшь разделять «шаблонизацию» и «логику представления».
    когда-нибудь ты научишься, оптимизировать «узкие места», а не что попало.
    а быть может я соврал, и ты никогда не постигнешь настоящего дао.

    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        как один из вариантов — да. хотя, меня не покидает идея сделать подобное на чистом php, где вместо xml-деревьев, будут деревья php-массиов и объектов ;-)
  • 0
    кто-нибудь пробовал шаблонизатор blitz? alexeyrybak.com/blitz/blitz_ru.html
    весьма удобная штука
    • +1
      Я пробовал. И насчет ее удобства я бы поспорил…
      • 0
        хорошо, спорить не буду, но вот чем не удобно интересно знать.
        • +1
          Ну, хотя бы тем, что там нет даже нормального if.
          • –4
            а зачем он ужен? о_0
  • 0
    Шо опять?
    Когда-то писал шабонизатор еще для PHP4 (http://code.google.com/p/hstemplate/)
    В результате что-либо лучше нежели Zend_View нарисовать врядли получиться (его замечательно юзать без самого фреймворка можно)
    • –2
      >В результате что-либо лучше нежели Zend_View нарисовать врядли получиться

      Синтаксис избыточный. Там, где в Zend_View нужно писать:

      <?php echo $this->escape($val['author']) ?>

      в том же Smarty пишется:

      {$author|escape}
  • –2
    Синтаксис взорвал мозг. Избыточность невероятная (и ничем не обоснованная).
  • –1
    О нет, неужели ещё один шалонизатор?!..
    • 0
      ага =)
  • 0
    Ужасное сокращение template => temp.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.