PHP

индекс
206,76

Наследование шаблонов в 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 в комплект поставки, естественно, не входит).

Спасибо за внимание :)
+40
25 августа 2008, 20:18
76

комментарии (54)

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

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

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

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

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

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

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

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

{container name=«layout.tpl»}
Something here…
{/container}

В layout.tpl вложенный код вставляется тэгом {$_output}

ЗЫ Не забудьте написать обработку за*loop*ленных включений шаблонов ;)
0
marazmiki #
Если честно, не совсем понял, где в Вашем случае наследование… Расскажите поподробнее, пожалуйста.

А насчёт «ЗЫ»: что такое за*loop*ленные включения шаблонов? Если это просто инклюд шаблона внутри {loop}{/loop} или {foreach}{/foreach}, то, как мне кажется, проблем возникнуть не должно. Если ошибся — поправьте. Желательно с примерами :)
0
sEXEcutor #
Это не наследование, как вы это понимаете, а немного другой метод подстановки — контейнер, когда любой кусок кода может быть обернут в другой шаблон.

А простой пример залупления :) — если вы напишете в файле index.tpl:
{include file=«index.tpl»}

В плагине наследования/контейнера может быть так, что в каком-то из родительских шаблонов обрабатывается снова какой-то из подшаблонов. Например, если в вашем parent.tpl написать:

{extends template=«child.tpl»}bla-bla-bla{/extends}

всё закончится очень печально. Цепочка подключений может быть длиннее, что затруднит поиск повторного инклуда. Ситуация смоделированная, но вполне реальная в условиях по-другому спроектированной обширной системы.
+2
fxposter #
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
fxposter #
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
fxposter #
Мдя… И что делать если я не дописал комментарий, он вставился непонятно с какой радости, да еще и при попытке его дописать — хабрахабр опять его вставил… :(
0
marazmiki #
Если честно, я боялся этого вопроса и вероломно о нём умолчал. С другой стороны приятно, что я заставил кого-то задуматься :)

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

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

У меня есть класс 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
K1N6 #
$this->_blocks[$key][count($this->_blocks[$key])-1]
лучше заменить на
end($this->_blocks[$key])
наверное
0
crYpt #
При использовании вашего плагина для наследования шаблонов обнаружил несколько проблем:
1. базовый шаблон загружается дважды (при открывающем теге extends и при закрывающем)
0
crYpt #
При использовании вашего плагина для наследования шаблонов обнаружил несколько проблем:
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
Arsenichev #
Данный подход вызывает проблемы при использовании фильтров вывода, например сжатия страницы перед вызовом функции display:
$this->register_outputfilter( array(&$this, 'gz_compress') );

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

Решения проблемы пока не нашел :(
+1
kid #
Вариант решения проблемы с 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
kid #
Вариант решения проблемы с 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
col #
Попробовал, понравилось.
Всё было хорошо пока не включил кэширование в Smarty.
Наследование тут же отвалилось, пробую понять что к чему, но изнутри Samarty знаю плохо.

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

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