При разработке Web-приложений мы обязательно сталкиваемся с проблемами рендеринга HTML-страниц. Обычно эти проблемы решает шаблонизатор — собственно PHP или какой-нибудь парсер шаблонов. Если приложение большое и страницы содержат множество блоков, то сложность шаблонов может резко возрасти, а у разработчиков появляется желание упростить работу с ними. В ход идут разные техники, но обычно это выделение в шаблонах повторяющихся блоков и правильная их декомпозиция — включая наследование шаблонов.
Мне нравится, как сделано наследование шаблонов в Django. Идея простая — есть базовый шаблон, в нем выделены контентные блоки, которые будут меняться в зависимости от страницы. При рендеренге страницы можно указать, что за основу берется базовый шаблон и переопределить только нужные блоки. Если в проекте много однотипных страниц, то можно сделать промежуточный шаблон, наследующий от главного, а затем переопределять его данные. При умелом проектировании количество повторяющегося кода можно свести на нет, а также облегчить жизнь при изменении дизайна.
Похоже этот подход нравится не только мне и разработчикам Django, но и Fabien Potencier автору фреймворка Symfony и шаблонизатора Twig. Twig обладает множеством интересных функций, включая компиляцию шаблонов в нативные PHP-классы, фильтрацию данных, встроенными циклами и т.д. — в общем всем тем, что полагается иметь современному шаблонизатору. Но самое интересное — это то самое наследование шаблонов, о котором я говорил выше. Вот пример из официальной документации:
Базовый шаблон:
Дочерний шаблон:
В базовом шаблоне определены основные блоки, а в дочернем — пример их переопределения. Копания в исходных кодах показало, что реализуется это наследование также нативно — путем наследования “скомпилированных” классов. Отличная идея! Все хорошо, но меня несколько смущала необходимость изучения пусть простого, но все-таки отличного от PHP синтаксиса шаблонизатора. А моя нелюбовь к ненативным шабонам началась еще со Smarty (в котором тоже есть наследование) и до сегодняшнего дня не претерпела существенных изменений.
Совсем близко к иделу подошел разработчик из Сан-Франциско Adam Shaw. Очевидно, он также как и я не любит эксперименты с синтаксисом шаблонизаторов и придумал незамысловато названную библиотеку Template Inheritance На сайте жирным по желтому написано, что «There is no need to learn another template language», мол не нужно учить другой язык шаблонов. С этим я согласен. Что же он предлагает? Смотрим пример опять же из официальной документации:
Базовый шаблон:
Дочерний шаблон:
Синтаксис натуральный, блоки выделены явно, подключил библиотеку в базовый шаблон и забыл. Все. Автор говорит, что сделал это с помощью буферов и стека (возможны вложенные блоки). Код действительно интересный, но пестрит наличием глобальных переменных. Чего же остается ещё желать?
Вот здесь-то мы и подходим к главной теме нашего повествования. А не сможет ли PHP сам переопределить блоки базового шаблона? Я думаю, что вполне! Смотрите:
Вот базовый шаблон:
Здесь в 3 блоках шаблона проверяется наличие соответствующей переменной, хранящей некоторый контент и, если она присутствует в области видимости, то ее можно выводить в шаблон, а если нет, то выводится контент по-умолчанию. Нам остается только переопределить эти переменные в дочернем шаблоне. А вот собственно и он:
В этом примере переопределяется переменная $content, если она не была установлена заранее. Это сделано для того, чтобы была возможность наследовать этот шаблон и переопределить блок контента. Думаю, идея понятна. Не требуется никаких библиотек — просто пишите шаблоны в таком стиле и будет вам счастье.
Конечно, и здесь не обошлось без недостатков. Во-первых, это не очень лаконичный синтаксис определения и переопределения блоков: расплата за нативность. Во вторых, в дочернем шаблоне нельзя получить код родительского блока. В-третьих, при таком способе включения шаблонов перед собственно HTML-кодом может образоваться некоторое количество пробелов из-за отступов между блоками. Здесь я бы посоветовал подключать шаблон также с помощью буферов и фильтровать контент. Так делается во многих фреймворках:
Эта функция возвращает вывод шаблона из файле $pathToTemplate с подстановкой переменных, полученных из массива $data. extract — на любителя — можно и не делать, а обращаться напрямую к $data. Перед выводом из контента убираются начальные и конечные пробелы. В шаблоне можно делать все, что позволит делать ваша совесть, не нарушая принципы разделения логики и представления, и PHP. Например, в зависимости от ситуации подключать тот или иной базовый файл.
Вот и все. Для реализации наследования можно использовать любой из описанных выше методов, уверен, что есть еще что-то. Буду рад, если эта статья поможет кому-то сделать свой код немного лучше.
Мне нравится, как сделано наследование шаблонов в Django. Идея простая — есть базовый шаблон, в нем выделены контентные блоки, которые будут меняться в зависимости от страницы. При рендеренге страницы можно указать, что за основу берется базовый шаблон и переопределить только нужные блоки. Если в проекте много однотипных страниц, то можно сделать промежуточный шаблон, наследующий от главного, а затем переопределять его данные. При умелом проектировании количество повторяющегося кода можно свести на нет, а также облегчить жизнь при изменении дизайна.
Похоже этот подход нравится не только мне и разработчикам Django, но и Fabien Potencier автору фреймворка Symfony и шаблонизатора Twig. Twig обладает множеством интересных функций, включая компиляцию шаблонов в нативные PHP-классы, фильтрацию данных, встроенными циклами и т.д. — в общем всем тем, что полагается иметь современному шаблонизатору. Но самое интересное — это то самое наследование шаблонов, о котором я говорил выше. Вот пример из официальной документации:
Базовый шаблон:
<!DOCTYPE html> <html> <head> {% block head %} <link rel="stylesheet" href="style.css" /> <title>{% block title %}{% endblock %} - My Webpage</title> {% endblock %} </head> <body> <div id="content">{% block content %}{% endblock %}</div> <div id="footer"> {% block footer %} © Copyright 2011 by <a href="http://domain.invalid/">you</a>. {% endblock %} </div> </body> </html>
Дочерний шаблон:
{% extends "base.html" %} {% block title %}Index{% endblock %} {% block head %} {{ parent() }} <style type="text/css"> .important { color: #336699; } </style> {% endblock %} {% block content %} <h1>Index</h1> <p class="important"> Welcome on my awesome homepage. </p> {% endblock %}
В базовом шаблоне определены основные блоки, а в дочернем — пример их переопределения. Копания в исходных кодах показало, что реализуется это наследование также нативно — путем наследования “скомпилированных” классов. Отличная идея! Все хорошо, но меня несколько смущала необходимость изучения пусть простого, но все-таки отличного от PHP синтаксиса шаблонизатора. А моя нелюбовь к ненативным шабонам началась еще со Smarty (в котором тоже есть наследование) и до сегодняшнего дня не претерпела существенных изменений.
Совсем близко к иделу подошел разработчик из Сан-Франциско Adam Shaw. Очевидно, он также как и я не любит эксперименты с синтаксисом шаблонизаторов и придумал незамысловато названную библиотеку Template Inheritance На сайте жирным по желтому написано, что «There is no need to learn another template language», мол не нужно учить другой язык шаблонов. С этим я согласен. Что же он предлагает? Смотрим пример опять же из официальной документации:
Базовый шаблон:
<?php require_once 'ti.php' ?> <html> <body> <h1> <?php startblock('title') ?> <?php endblock() ?> </h1> <div id='article'> <?php startblock('article') ?> <?php endblock() ?> </div> </body> </html>
Дочерний шаблон:
<?php include 'base.php' ?> <?php startblock('title') ?> This is the title <?php endblock() ?> <?php startblock('article') ?> This is the article <?php endblock() ?>
Синтаксис натуральный, блоки выделены явно, подключил библиотеку в базовый шаблон и забыл. Все. Автор говорит, что сделал это с помощью буферов и стека (возможны вложенные блоки). Код действительно интересный, но пестрит наличием глобальных переменных. Чего же остается ещё желать?
Вот здесь-то мы и подходим к главной теме нашего повествования. А не сможет ли PHP сам переопределить блоки базового шаблона? Я думаю, что вполне! Смотрите:
Вот базовый шаблон:
<!DOCTYPE HTML> <html lang="ru-RU"> <head> <title><?php echo isset($title) ? $title : ''; ?></title> <meta charset="UTF-8"> </head> <body> <div class="content"> <?php if(isset($content)){echo $content}else{ ?> Default content <?php }?> </div> <div class="sidebar"> <?php if(isset($sidebar)){echo $sidebar}else{ ?> Default sidebar <?php }?> </div> </body> </html>
Здесь в 3 блоках шаблона проверяется наличие соответствующей переменной, хранящей некоторый контент и, если она присутствует в области видимости, то ее можно выводить в шаблон, а если нет, то выводится контент по-умолчанию. Нам остается только переопределить эти переменные в дочернем шаблоне. А вот собственно и он:
<?php if(!isset($content)){ ob_start(); ?> <h1>Переопределенный контент</h1> <?php $content = ob_get_clean();} ?> <?php require 'baseTemplate.php'; ?>
В этом примере переопределяется переменная $content, если она не была установлена заранее. Это сделано для того, чтобы была возможность наследовать этот шаблон и переопределить блок контента. Думаю, идея понятна. Не требуется никаких библиотек — просто пишите шаблоны в таком стиле и будет вам счастье.
Конечно, и здесь не обошлось без недостатков. Во-первых, это не очень лаконичный синтаксис определения и переопределения блоков: расплата за нативность. Во вторых, в дочернем шаблоне нельзя получить код родительского блока. В-третьих, при таком способе включения шаблонов перед собственно HTML-кодом может образоваться некоторое количество пробелов из-за отступов между блоками. Здесь я бы посоветовал подключать шаблон также с помощью буферов и фильтровать контент. Так делается во многих фреймворках:
function render($pathToTemplate, $data) { extract($data); ob_start(); require $pathToTemplate; return trim(ob_get_clean()); }
Эта функция возвращает вывод шаблона из файле $pathToTemplate с подстановкой переменных, полученных из массива $data. extract — на любителя — можно и не делать, а обращаться напрямую к $data. Перед выводом из контента убираются начальные и конечные пробелы. В шаблоне можно делать все, что позволит делать ваша совесть, не нарушая принципы разделения логики и представления, и PHP. Например, в зависимости от ситуации подключать тот или иной базовый файл.
Вот и все. Для реализации наследования можно использовать любой из описанных выше методов, уверен, что есть еще что-то. Буду рад, если эта статья поможет кому-то сделать свой код немного лучше.
