Как стать автором
Обновить

Наследование шаблонов в PHP без использования сторонних библиотек

Время на прочтение5 мин
Количество просмотров8K
При разработке Web-приложений мы обязательно сталкиваемся с проблемами рендеринга HTML-страниц. Обычно эти проблемы решает шаблонизатор — собственно 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. Например, в зависимости от ситуации подключать тот или иной базовый файл.

Вот и все. Для реализации наследования можно использовать любой из описанных выше методов, уверен, что есть еще что-то. Буду рад, если эта статья поможет кому-то сделать свой код немного лучше.
Теги:
Хабы:
Всего голосов 44: ↑36 и ↓8+28
Комментарии58

Публикации

Истории

Работа

PHP программист
114 вакансий

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань