Наследование шаблонов в Smarty

    Когда-то, давным-давно, мне пришлось использовать небезызвестный шаблонизатор Smarty. Сначала я, понятное дело, возмущался и кричал, какая же гадость эта заливная рыба Smarty, а потом «распробовал» и втянулся. Те удобства, которые он давал, с лихвой компенсировали мысли о том, что есть и более быстрые шаблонные движки.

    Шаблоны я обычно строил с помощью инклюдов: в начале подключался header.tpl, в конце — footer.tpl, в середине ещё что-нибудь нужное. В целом разметка получалась довольно аккуратной, но не проходило ощущение, что не хватает чего-то важного. Окончательно понимание этого чего-то появилось, когда мне случилось написать простенькое приложение на Django. И это «что-то», как все поняли, оказалось наследованием шаблонов. Простая, как и всё гениальное, идея позволяла существенно упростить шаблоны и избавиться от дублирующих блоков.



    Решение оказалось не сложнее самой идеи наследования, которая, напомню, была простой, как и всё гениальное :)
    Примечание: дабы не плодить сущего, я не буду пересказывать статью про наследование шаблонов в Django, однако рекомендую её прочитать, дабы примерно понять, что нас ждёт и чтобы по исходным текстам шаблонов можно было понять, что они делают
    Вопреки расхожему мнению, одной из главных задач Smarty является не банальная замена <?php echo $var ?> более лаконичными {$var}, а расширение базовой функциональности плагинами. В частности, Smarty позволяет определять собственные блоковые функции. Именно этим и воспользуемся.
    Примечание: в отличие от Django, здесь будет использован не одиночный тег {% extend %}, а блок {extends}...{/extends}, в пределах которого будут располагаться наследуемые блоки. Сделано это было, во-первых, из-за простоты реализации, во-вторых — этот подход даёт возможность наследовать разные шаблоны (хорошо это плохо — вопрос другой; в крайнем случае, никто не заставляет использовать несколько блоков {extends} в одном шаблоне).
    Синтаксис шаблонов наследования будет примерно таким:
    parent.tpl:
    <html>
    <head>
      <title> Inherit it! </title>
    </head>
    <body>
    <p>Just a paragraph</p>
    <p>{block name="foo"}It's a parent{/block}</p>
    </body>
    </html></pre>

    child.tpl:
    {extends template="parent.tpl"}
      {block name="foo"}It's a child{/block}
    {/extends}

    index.php:
    <?php 
    $smarty->display('child.tpl');
    ?>
    
    Особо, думаю, ничего пояснять не надо: перед компиляцией шаблона блок {extends} заменяется содержимым шаблона, который указан в параметре template блока. Все именованные блоки, которые были определены внутри {extends}, перекрывают соответствующие блоки в родительском шаблоне.

    А результат работы выглядит вот так:
    <html>
    <head>
      <title> Inherit it! </title>
    </head>
    <body>
    <p>Just a paragraph</p>
    <p>It's a child</p>
    </body>
    </html>
    Идея вкратце такова: внутри объекта шаблонизатора введём ассоциативный массив, ключами которого будут имена наследуемых блоков, а соответствующими им значениями — массивы, содержащие текстовые содержания этих блоков, хранящиеся в порядке их (блоков) вызова. Согласен, фраза получилась заумной, поэтому проще показать на предыдущем примере:
    Array
    (
        [foo] => Array
            (
                [0] => It's a parent
                [1] => It's a child
            )
    )
    Надеюсь, всё просто. Теперь остаётся при вызове блока в шаблоне «достать» из этого хранилища последний элемент и отобразить его на месте тегов :)

    Как я уже писал выше, для реализации нам понадобится зарегистрировать 2 блока с именами extends и block, а так же ввести хранилище значений.

    Пусть блок {extends}{/extends} будет отвечать за получение исходного кода шаблона-родителя, а {block}{/block} — за создание и переопределение наследуемых блоков.

    Мануал поможет нам создать блоковые плагины:
    block.extends.php:
    <?php
    
    /**
     * Блок, наследующий шаблон
     * 
     * @param  array   $params   Список параметров, указанных в вызове блока
     * @param  string  $content  Текст между тегами {extends}..{/extends}
     * @param  mySmarty  $smarty   Ссылка на объект Smarty
     */
    function smarty_block_extends($params, $content, mySmarty $smarty)
    {
        /** Никому не доверяйте. Даже себе! */
        if (false === array_key_exists('template', $params)) {
            $smarty->trigger_error('Укажите шаблон, от которого наследуетесь!');
        }
    
        return $smarty->fetch($params['template']);
    }
    
    ?>
    
    block.block.php:
    <?php
    
    /**
     * Создаёт именованные блоки в тексте шаблона
     * 
     * @param  array   $params   Список параметров, указанных в вызове блока
     * @param  string  $content  Текст между тегами {extends}..{/extends}
     * @param  mySmarty  $smarty   Ссылка на объект Smarty
     */
    function smarty_block_block($params, $content, mySmarty $smarty)
    {
        if (array_key_exists('name', $params) === false) {
            $smarty->trigger_error('Не указано имя блока');
        }
    
        $name = $params['name'];
    
        if ($content) {
            $smarty->setBlock($name, $content);
        }
    
        return $smarty->getBlock($name);
    }
    
    Здесь надо сказать, что setBlock() и getBlock() — методы шаблонизатора, которые соответственно помещают и получают текстовые значения наследуемых блоков из стека, про который было сказано выше. Расширим класс Smarty, введя массив стека и методы:

    mySmarty.class.php
    <?php
    
    class mySmarty extends Smarty
    {
        /**
         * Список зарегистрированных блоков в шаблонизаторе
         *
         * @var  array
         */
        protected $_blocks = array();
    
        /**
         * Конструктор класса
         *
         * @param   void
         * @return  void
         */
        public function __construct()
        {
            $this->Smarty();
        }
    
        /**
         * Регистрирует наследуемый блок шаблона
         *
         * @param   string  $key
         * @param   string  $value
         * @return  void
         */
        public function setBlock($key, $value)
        {
            if (array_key_exists($key, $this->_blocks) === false) {
                $this->_blocks[$key] = array(); 
            }
    
            if (in_array($value, $this->_blocks[$key]) === false) {
                array_push($this->_blocks[$key], $value);
            }
        }
    
        /**
         * Возвращает код блока согласно иерархии наследования
         *
         * @param   string  $key
         * @return  string
         */
        public function getBlock($key)
        {
            if (array_key_exists($key, $this->_blocks)) {
                return $this->_blocks[$key][count($this->_blocks[$key])-1];
            }
    
            return '';
        }
    }
    ?>


    Теперь, подключив mySmarty.class.php, можно создавать объект класса mySmarty и пользоваться прелестями наследования шаблонов.

    Ленивые могут скачать готовый пример шаблонов и пощупать на деле (архив весит 2.2 кб, Smarty в комплект поставки, естественно, не входит).

    Спасибо за внимание :)

    Комментарии 54

      +5
      шикарно :) ещё один повод любить смарти!
        +2
        Справедливости ради надо сказать, что подобная фишка сработает много где, даже на pure php её достаточно просто реализовать.

        Но спасибо за положительный отзыв :)
          +2
          я смарти люблю в первую очередь именно за гибкость, которая достигается плагинами… не так давно построил на них чуть ли ни целый фреймворк :)
            0
            Делитесь идеями, они важны =)
              0
              Делитесь идеями, многим полезно будет :)
                +2
                Такими темпами функциональность smarty доведут до уровня самого php и таки напишут шаблонизатор на шаблонизаторе, написанном на языке, который сам в свою очередь изначально был шаблонизатором. Прямо дух захватывает от такой многоуровневости ;)
                  0
                  Подробнее-подробнее!
              +1
              как уже правильно заметил сам автор, это совсем не крутизна Смарти. Это идея, которую можно применить где угодно. Идея хорошая, спасибо.
                +2
                Мне в смарти доставляет удовольствие его способность кешировать. Объявил какой либо шаблон, что наполняется данными, закешировал и всё база не вызывается лишний раз. Правда в одном из моих проектов таким способом бывает накешируется до 50 — 60 мегабайт (2 000 — 3 000 файлов), не знаю при поиске шаблона из такого колличества файлов — что быстрее — запрос к базе или же перебор смарти при поиске закешированного шаблона.
                  0
                  Ну… тема кеширования в шаблонах — тема для отдельной статьи. И тема эта, надо сказать, противоречивая :)
                    0
                    хранить кэши блоков и страниц можно и не в файловой системе.

                    правда, обычно это делается не на уровне шаблонизатора.

                    например, для Drupal есть модуль cache router, который позволяет распределять кэшируемые объекты по разным хранилищам (файловая система, БД, memcached.
                    +1
                    function smarty_block_block($params, $content, Smarty $smarty) — здесь имеется неточность: type-hinting нужно указывать как mySmarty $smarty — ведь в оригинальном Smarty нет методов set/getBlock.
                      0
                      Не думаю, что кто-то в пределах одного проекта будет использовать и Smarty и его расширенную версию. Однако, Вы правы — это баг. Исправлю.
                      +2
                      Около года назад имел дело с наследованием в смарти и решение использ-овал несколько отличное от этого.
                      Вечером постараюсь дома написать топик на хабр со своим вариантом.
                        0
                        Будет очень интересно узнать :)
                        0
                        Не раз слышал критику Smarty, один из основных недостатков называли — отсутствие наследования шаблонов :)

                        Спасибо за решение.
                          0
                          За решание — пожалуйста :)

                          А насчёт критики — занятно. Все обычно акцентируют внимание на тормознутости и «интерпретируемый язык на интерпретируемом языке», про отсутствие наследования никто не вспоминал; я, если честно, вообще не знаю на PHP шаблнизаторов с реализованным уже наследованием. Они, похоже, не существуют или не получили широкого распространения.
                            0
                            Ну, честно говоря, никогда не сталкивался с их жесткой необходимостью. Это интересная фишка на заметку, но зачастую она вам не пригодится. Потому она и не получила широкого распространения ;)
                              0
                              Про «не пригодится» не соглашусь.

                              Жёсткой необходимости нет почти ни в чём. Можно ведь и не заморачиваться на ООП в целом и Смарти в частности, а просто писать код линейно и потоком, не заморачиваясь на такие мелочи, как разделение логик приложения и отображения :). Вопрос в том, насколько потом ЭТО будет легко поддерживаться.

                              То же самое и с шаблонами. Я уже на собственном опыте убедился, что подобный подход проще в дальнейшем расширении. Особенно актуально для создания новых шаблонов на основе уже существующих :)
                                0
                                ну в том же ZF вроде как можно из шаблона вызывать другие контроллеры (а не тот который отрабатывается сейчас). И основной контроллер совершенно не обязан о них думать. В шаблон добавляем $this->action() и понеслась
                              0
                              xslt. и не только на пхп…
                            0
                            Это же слоты, правда?
                              0
                              Хоть убейте меня, но я не понимаю чем это отличается от include?
                                +2
                                тем, что инклуд не меняется. В случае одного блока вам понадобится два инклуда код_до и код после. В случае двух блоков, уже три итд. В случае наследования, вам всегда нужен один (если наследуемся от одного конечно).
                                  0
                                  был бы благодарен примером :)
                                    +1
                                    он есть в посте. Там два файла parent.tpl и child.tpl в случае инклуда вам бы понадобились хидер и футер, вместо парента. Добавьте еще блок и вам понадобится хидер, футер и миддлер (гыгы).
                                      0
                                      Спасибо.
                              • НЛО прилетело и опубликовало эту надпись здесь
                                  0
                                  Если честно, не совсем понял, где в Вашем случае наследование… Расскажите поподробнее, пожалуйста.

                                  А насчёт «ЗЫ»: что такое за*loop*ленные включения шаблонов? Если это просто инклюд шаблона внутри {loop}{/loop} или {foreach}{/foreach}, то, как мне кажется, проблем возникнуть не должно. Если ошибся — поправьте. Желательно с примерами :)
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                  +2
                                  if (in_array($value, $this->_blocks[$key]) === false) { array_push($this->_blocks[$key], $value); }

                                  Вот этот момент, по-моему, лишний. Если я присвоил блоку значение, потом его переопределил, а потом хочу переопределить на предыдущее (хоть я этого, наверное, ни разу и не делал), то я хочу всё же получить последнее переопределенное значение. А так получается, что я могу написать:

                                  parent.tpl:

                                  Inherit it!

                                  Just a paragraph
                                  {block name=«foo»}It's a parent{/block}

                                  {extends template=«parent.tpl»}
                                  {block name=«foo»}It's a child{/block}
                                  {/extends}
                                    0
                                    if (in_array($value, $this->_blocks[$key]) === false) { array_push($this->_blocks[$key], $value); }

                                    Вот этот момент, по-моему, лишний. Если я присвоил блоку значение, потом его переопределил, а потом хочу переопределить на предыдущее (хоть я этого, наверное, ни разу и не делал), то я хочу всё же получить последнее переопределенное значение. А так получается, что я могу написать:

                                    <code>{block name=«foo»}It's a parent{/block}</code>


                                    <code>{extends template=«parent.tpl»}
                                    {block name=«foo»}It's a child{/block}
                                    {/extends}</code>


                                    <code>{extends template=«parent.tpl»}
                                    {block name=«foo»}It's a child{/block}
                                    {/extends}</code>
                                      0
                                      Мдя… И что делать если я не дописал комментарий, он вставился непонятно с какой радости, да еще и при попытке его дописать — хабрахабр опять его вставил… :(
                                      0
                                      Если честно, я боялся этого вопроса и вероломно о нём умолчал. С другой стороны приятно, что я заставил кого-то задуматься :)

                                      Дело тут в том, что если эту проверку убрать, то в массив будет дампаться всё дерево наследования. И последним элементом, разумеется, будет «корень». Если непонятно, просто сделайте var_dump($template), увидите, что я имею в виду.

                                      Я пошёл на это ограничение, исходя именно из тех соображений, что случаи, когда «внук» будет точно таким же, как его «дедушка», довольно редки и ими можно пренебречь :)
                                      0
                                      А вообще — спасибо за статью, коротко и ясно. Мне этого, честно говоря, не хватало! :)
                                        0
                                        А чем хуже вынести содержимое блока, который нужно подменить во внешний файл (в несколько внешних файлов) и в зависимости от параметров эти файлы подключать? Получится композиция вместо наследования, что по сути есть более гибкое решение.
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                            +1
                                            Я отказался от смарти когда стал использовать больше ООП.
                                            ведь конструкция типа
                                            {$item->getColorById(1)->getSizeById(2)->name}
                                            не работала :-(
                                              0
                                              Это элементарно исправляется.
                                                +1
                                                подскажите?
                                                знаю только что нужно регекспы патчить, но они там такие, что чёрт ногу сломит…
                                                  0
                                                  Да, нужно исправить одно регулярное выражение для разыменования объектов и методов и второе — для доступа к публичным свойствам класса. Если интересно, ближе к вечеру выложу
                                                0
                                                А мои бывшие коллеги до сих пор его используют. Изобретая чудовищные костыли для обхода вот таких вот недоработок.
                                                Хотя ходят слухи, что где-то существует приватная версия смарти, которая позволяет вызывать методы объектов цепочкой… ;)
                                                  0
                                                  Хотите увидеть эту приватную версию? :))
                                                    0
                                                    Разве только в виде патча. И то, интерес чисто академический, т.к. Smarty не пользую уже давно. И, если всё будет хорошо, не буду и в дальнейшем.
                                                      0
                                                      извиняюсь, что вторгаюсь в беседу, но уж очень любопытно глянуть на исправленную версию Smarty…
                                                    0
                                                    решение проблеммы {$item->getColorById(1)->getSizeById(2)->name}
                                                    для смарти здесь:
                                                    habrahabr.ru/blogs/php/45651/#comment_1159897
                                                    0
                                                    нашел еще пару интересных особенностей этого решения.
                                                    Приведу реальный пример из своей практики.

                                                    У меня есть класс Application. Он хранит сообщения об ошибках (например неправильно заполнена форма) во внутреннем массиве $_messages. Сообщения добавляются в массив следующим методом:
                                                        public function addMessage($text, $type = 'Error') {
                                                            if (empty($text)) return true;
                                                            
                                                            if (empty($type))
                                                            $type = 'Error';
                                                            $this->_messages[$type][] = $text;
                                                            return true;
                                                        }
                                                    

                                                    А извлекаются следующим методом:
                                                        public function getMessages($type = 'Error') {
                                                            if (empty($type)) {
                                                                $type = 'Error';
                                                            }
                                                            $aMessages = isset($this->_messages[$type]) ? $this->_messages[$type] : false;
                                                            $this->_messages[$type] = null;
                                                            return $aMessages;
                                                    
                                                        }
                                                    


                                                    А выводятся эти сообщения в шаблоне _errors.tpl, который инклудится в основном шаблоне layout.tpl, таким вот образом:
                                                    {assign var="aFailureMessages" value=$Application->getMessages('Error')}
                                                    {assign var="aSuccessfulMessages" value=$Application->getMessages('Success')}
                                                    {assign var="aInformativeMessages" value=$Application->getMessages('Informative')}
                                                    
                                                    {if $aFailureMessages || $aSuccessfulMessages || $aInformativeMessages}
                                                        <table width="100%" cellspacing="0" cellspacing="0" border="0">
                                                            {if $aFailureMessages}
                                                            <tr>
                                                                <td class="error_box_red">
                                                                    {section name=failure loop=$aFailureMessages}
                                                                        {$aFailureMessages[failure]}
                                                                    {/section}
                                                                </td>
                                                            </tr>
                                                            <tr><td> </td></tr>
                                                            {/if}
                                                    
                                                            {if $aSuccessfulMessages}
                                                            <tr>
                                                                <td class="error_box_green">
                                                                    {section name=success loop=$aSuccessfulMessages}
                                                                        {$aSuccessfulMessages[success]}
                                                                    {/section}
                                                                </td>
                                                            </tr>
                                                            <tr><td> </td></tr>
                                                            {/if}
                                                    
                                                            {if $aInformativeMessages}
                                                            <tr>
                                                                <td class="error_box_blue">
                                                                    {section name=informative loop=$aInformativeMessages}
                                                                        {$aInformativeMessages[informative]}
                                                                    {/section}
                                                                </td>
                                                            </tr>
                                                            <tr><td> </td></tr>
                                                            {/if}
                                                        </table>
                                                    {/if}
                                                    


                                                    Т.е. логика работы этого механизма такова, что если сообщение было выведено для просмотра значит его уже не нужно выводить.

                                                    Но каково было мое удивление, когда применяя приведенный здесь механизм наследования шаблонов, я не увидел ни единого сообщения, добавленного в Application.

                                                    В ходе отладки выяснилось, что метод getMessage вызывается по 2 раза на каждый тип сообщений, т.е. шаблон рендерится дважды!!!
                                                    Если посмотреть документацию smarty по блоковым функциям, коими являются smarty_block_extends и smarty_block_block, то они вызываются по два раза, при открытии и закрытии соответствующих тегов smarty {extends} и {block}.

                                                    В случае с smarty_block_extends дважды вызовется метод $smarty->fetch!!! вот тут собака и порылась. Таким образом, родительский шаблон рендерится дважды. Но это не оптимально — раз, и приводит к печальному результату с моими сообщениями — два.

                                                    Чтобы этого избежать, нужно рендерить родительский шаблон всего лишь один раз, когда у нас имеются данные для подстановки в теги {block}, когда был обработан шаблон-потомок, а именно во время второго вызова smarty_block_extends.

                                                    Внутри функции smarty_block_extends ставим проверку вида:
                                                        if (!is_null($content)) {
                                                            return $smarty->fetch($params['template'], ...);
                                                        }
                                                        return false;
                                                    


                                                    Казалось бы, проблема решена, но! Во время переназначения содержимого блоков в таком случае данные push'атся в обратном порядке, т.е. содержимое блока из шаблона-наследника будет не на ВЕРШИНЕ стека, а на его ДНЕ. Т.е при получении содержимого блока при помощи метода getBlock, мы получим значение блока из родительского шаблона.

                                                    Решение: заменить в методе setBlock
                                                    array_push($this->_blocks[$key], $value);
                                                    

                                                    на
                                                    array_unshift($this->_blocks[$key], $value);
                                                    
                                                      0
                                                      $this->_blocks[$key][count($this->_blocks[$key])-1]
                                                      лучше заменить на
                                                      end($this->_blocks[$key])
                                                      наверное
                                                        0
                                                        При использовании вашего плагина для наследования шаблонов обнаружил несколько проблем:
                                                        1. базовый шаблон загружается дважды (при открывающем теге extends и при закрывающем)
                                                          0
                                                          При использовании вашего плагина для наследования шаблонов обнаружил несколько проблем:
                                                          1. базовый шаблон загружается дважды (при открывающем теге extends и при закрывающем)
                                                          2. если вставляю два наследуемых блока, а внутренний блок расширяю только для одного, то для второго будет подставлено содержимое первого вставленного блока. напрмер:
                                                          a.tpl
                                                          ----
                                                          {block name=head}parent{/block}
                                                          ----
                                                          
                                                          b.tpl
                                                          ----
                                                          {extends template="a.tpl"}
                                                          {block name="head"}child{/block}
                                                          {/extends}
                                                          
                                                          {extends template="a.tpl"}{/extends}
                                                          ---
                                                          


                                                          при отображении b.tpl будет выведено:
                                                          child
                                                          
                                                          child
                                                          


                                                          Следующий код позволяет решить эту проблему:

                                                          плагины:
                                                          <?php
                                                          
                                                          function smarty_block_block($params, $content, $smarty)
                                                          {
                                                              if ( !array_key_exists('name', $params) )
                                                              {
                                                                  $smarty->trigger_error('Block name is not set');
                                                              }
                                                          
                                                              $name = $params['name'];
                                                          
                                                              if ( !$smarty->isBlockSet($name) && !is_null($content) )
                                                              {
                                                                  $smarty->setBlock($name, $content);
                                                              }
                                                          
                                                              if ( !is_null($content) )
                                                              {
                                                                  return $smarty->getBlock($name);
                                                              }
                                                          }
                                                          
                                                          ?>
                                                          
                                                          <?php
                                                          
                                                          function smarty_block_extends($params, $content, $smarty)
                                                          {
                                                              if ( !array_key_exists('template', $params) )
                                                              {
                                                                  $smarty->trigger_error('Plese set extending template name!');
                                                              }
                                                              
                                                              // if open tag
                                                              if ( is_null($content) )
                                                              {
                                                                  $smarty->openBlocksScope();
                                                              }
                                                              else
                                                              {
                                                                  $content = $smarty->fetch($params['template']);
                                                                  $smarty->closeBlocksScope();
                                                                  return $content;
                                                              }
                                                              return '';
                                                          }
                                                          
                                                          ?>
                                                          


                                                          поля и методы класса SmartyX (extends Smarty)

                                                          
                                                              protected $_blocks = array(array());
                                                              protected $_blocksScope = 0;
                                                          
                                                          ...
                                                          
                                                              public function openBlocksScope()
                                                              {
                                                                  $this->_blocksScope++;
                                                                  $this->_blocks[$this->_blocksScope] = array();
                                                              }
                                                              
                                                              public function closeBlocksScope()
                                                              {
                                                                  if ( $this->_blocksScope > 0 )
                                                                  {
                                                                      $this->_blocks[$this->_blocksScope] = array();
                                                                      $this->_blocksScope--;
                                                                  }
                                                              }
                                                              
                                                              public function isBlockSet($key)
                                                              {
                                                                  return array_key_exists($key, $this->_blocks[$this->_blocksScope]) !== false;
                                                              }
                                                          
                                                              public function setBlock($key, $value)
                                                              {
                                                                  $this->_blocks[$this->_blocksScope][$key] = $value;
                                                              }
                                                          
                                                              public function getBlock($key)
                                                              {
                                                                  if (array_key_exists($key, $this->_blocks[$this->_blocksScope]))
                                                                  {
                                                                      return $this->_blocks[$this->_blocksScope][$key];
                                                                  }
                                                                  return '';
                                                              }
                                                          
                                                          

                                                            0
                                                            Данный подход вызывает проблемы при использовании фильтров вывода, например сжатия страницы перед вызовом функции display:
                                                            $this->register_outputfilter( array(&$this, 'gz_compress') );

                                                            Проблема заключается в том что обвертываемый шаблон проходит дважды через фильтр вывода (первый раз при вызове функции $smarty->fetch($params['template']); а второй уже при выводе шаблона display()).

                                                            Решения проблемы пока не нашел :(
                                                              +1
                                                              Вариант решения проблемы с output фильтрами:

                                                              0. Добавляете в свой унаследованный класс smarty поле is_page
                                                              public $is_page = false;

                                                              1. создаёте smarty-функцию {eop}
                                                              /**
                                                              * End Of Page
                                                              * @return
                                                              * @param object $params
                                                              * @param object $smarty
                                                              */
                                                              function smarty_function_eop($params, &$smarty)
                                                              {
                                                              $smarty->is_page = true;
                                                              }

                                                              2. В конце главного шаблона вызываете функцию:
                                                              </body>
                                                              </html>
                                                              {eop}

                                                              3. В самом начале output фильтра проверяете, а не конец ли страницы
                                                              function smarty_outputfilter_head($tpl_output, &$smarty)
                                                              {
                                                              if (!$smarty->is_page)
                                                              return $tpl_output;

                                                              }

                                                              4. Правите smarty_block_extends:
                                                              function smarty_block_extends($params, $content, $smarty, $repeat)
                                                              {
                                                              /** Никому не доверяйте. Даже себе! */
                                                              if (false === array_key_exists('template', $params)) {
                                                              $smarty->trigger_error('Укажите шаблон, от которого наследуетесь!');
                                                              }

                                                              if (!$repeat)
                                                              return $smarty->fetch('pages/'.$params['template']);
                                                              }
                                                              0
                                                              Вариант решения проблемы с output фильтрами:

                                                              0. Добавляете в свой унаследованный класс smarty поле is_page
                                                              public $is_page = false;

                                                              1. создаёте smarty-функцию {eop}
                                                              /**
                                                              * End Of Page
                                                              * @return
                                                              * @param object $params
                                                              * @param object $smarty
                                                              */
                                                              function smarty_function_eop($params, &$smarty)
                                                              {
                                                              $smarty->is_page = true;
                                                              }

                                                              2. В конце главного шаблона вызываете функцию:


                                                              {eop}

                                                              3. В самом начале output фильтра проверяете, а не конец ли страницы
                                                              function smarty_outputfilter_head($tpl_output, &$smarty)
                                                              {
                                                              if (!$smarty->is_page)
                                                              return $tpl_output;

                                                              }

                                                              4. Правите smarty_block_extends:
                                                              function smarty_block_extends($params, $content, $smarty, $repeat)
                                                              {
                                                              /** Никому не доверяйте. Даже себе! */
                                                              if (false === array_key_exists('template', $params)) {
                                                              $smarty->trigger_error('Укажите шаблон, от которого наследуетесь!');
                                                              }

                                                              if (!$repeat)
                                                              return $smarty->fetch('pages/'.$params['template']);
                                                              }
                                                                0
                                                                Попробовал, понравилось.
                                                                Всё было хорошо пока не включил кэширование в Smarty.
                                                                Наследование тут же отвалилось, пробую понять что к чему, но изнутри Samarty знаю плохо.

                                                                Сталкивались с подобным?

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

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