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

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

    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.


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

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

    Ну. И что?
    Реклама
    Комментарии 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
                                                                      Ужасное сокращение template => temp.

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

                                                                      Самое читаемое