— My Webpage

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

При разработке Web-приложений мы обязательно сталкиваемся с проблемами рендеринга HTML-страниц. Обычно эти проблемы решает шаблонизатор — собственно PHP или какой-нибудь парсер шаблонов. Если приложение большое и страницы содержат множество блоков, то сложность шаблонов может резко возрасти, а у разработчиков появляется желание упростить работу с ними. В ход идут разные техники, но обычно это выделение в шаблонах повторяющихся блоков и правильная их декомпозиция — включая наследование шаблонов.

Мне нравится, как сделано наследование шаблонов в Django. Идея простая — есть базовый шаблон, в нем выделены контентные блоки, которые будут меняться в зависимости от страницы. При рендеренге страницы можно указать, что за основу берется базовый шаблон и переопределить только нужные блоки. Если в проекте много однотипных страниц, то можно сделать промежуточный шаблон, наследующий от главного, а затем переопределять его данные. При умелом проектировании количество повторяющегося кода можно свести на нет, а также облегчить жизнь при изменении дизайна.

Похоже этот подход нравится не только мне и разработчикам Django, но и Fabien Potencier автору фреймворка Symfony и шаблонизатора Twig. Twig обладает множеством интересных функций, включая компиляцию шаблонов в нативные PHP-классы, фильтрацию данных, встроенными циклами и т.д. — в общем всем тем, что полагается иметь современному шаблонизатору. Но самое интересное — это то самое наследование шаблонов, о котором я говорил выше. Вот пример из официальной документации:

         © Copyright 2011 by you.  

 Index  > .important  

Index

Welcome on my awesome homepage.

В базовом шаблоне определены основные блоки, а в дочернем — пример их переопределения. Копания в исходных кодах показало, что реализуется это наследование также нативно — путем наследования “скомпилированных” классов. Отличная идея! Все хорошо, но меня несколько смущала необходимость изучения пусть простого, но все-таки отличного от PHP синтаксиса шаблонизатора. А моя нелюбовь к ненативным шабонам началась еще со Smarty (в котором тоже есть наследование) и до сегодняшнего дня не претерпела существенных изменений.

Читайте также:  Submitting form data javascript

Совсем близко к иделу подошел разработчик из Сан-Франциско Adam Shaw. Очевидно, он также как и я не любит эксперименты с синтаксисом шаблонизаторов и придумал незамысловато названную библиотеку Template Inheritance На сайте жирным по желтому написано, что «There is no need to learn another template language», мол не нужно учить другой язык шаблонов. С этим я согласен. Что же он предлагает? Смотрим пример опять же из официальной документации:

  This is the title  This is the article

Синтаксис натуральный, блоки выделены явно, подключил библиотеку в базовый шаблон и забыл. Все. Автор говорит, что сделал это с помощью буферов и стека (возможны вложенные блоки). Код действительно интересный, но пестрит наличием глобальных переменных. Чего же остается ещё желать?

Вот здесь-то мы и подходим к главной теме нашего повествования. А не сможет ли PHP сам переопределить блоки базового шаблона? Я думаю, что вполне! Смотрите:

      
else < ?>Default content ?>
else < ?>Default sidebar ?>

Здесь в 3 блоках шаблона проверяется наличие соответствующей переменной, хранящей некоторый контент и, если она присутствует в области видимости, то ее можно выводить в шаблон, а если нет, то выводится контент по-умолчанию. Нам остается только переопределить эти переменные в дочернем шаблоне. А вот собственно и он:

В этом примере переопределяется переменная $content, если она не была установлена заранее. Это сделано для того, чтобы была возможность наследовать этот шаблон и переопределить блок контента. Думаю, идея понятна. Не требуется никаких библиотек — просто пишите шаблоны в таком стиле и будет вам счастье.

Конечно, и здесь не обошлось без недостатков. Во-первых, это не очень лаконичный синтаксис определения и переопределения блоков: расплата за нативность. Во вторых, в дочернем шаблоне нельзя получить код родительского блока. В-третьих, при таком способе включения шаблонов перед собственно HTML-кодом может образоваться некоторое количество пробелов из-за отступов между блоками. Здесь я бы посоветовал подключать шаблон также с помощью буферов и фильтровать контент. Так делается во многих фреймворках:

function render($pathToTemplate, $data)

Эта функция возвращает вывод шаблона из файле $pathToTemplate с подстановкой переменных, полученных из массива $data. extract — на любителя — можно и не делать, а обращаться напрямую к $data. Перед выводом из контента убираются начальные и конечные пробелы. В шаблоне можно делать все, что позволит делать ваша совесть, не нарушая принципы разделения логики и представления, и PHP. Например, в зависимости от ситуации подключать тот или иной базовый файл.

Вот и все. Для реализации наследования можно использовать любой из описанных выше методов, уверен, что есть еще что-то. Буду рад, если эта статья поможет кому-то сделать свой код немного лучше.

Источник

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

На этом занятии поговорим о механизме расширения шаблонов в Jninja. Его еще называют наследованием. Это достаточно удобный инструмент, уменьшающий объем дублируемого кода в шаблонах. Рассмотрим принцип его работы на нашем примере с HTML-страницами сайта.

Для простоты восприятия возьмем целостную страницу и представим ее в таком виде (файл ex_main.htm):

DOCTYPE html> html> head> meta charset="UTF-8"> title>{% block title %}{% endblock %}/title> /head> body> {% block content %} {% endblock %} /body> /html>

Смотрите, здесь используется новый тип блоков – именованные блоки, которые в самом простом случае записываются по синтаксису:

Эти блоки как раз и используются для создания расширения базового шаблона страницы ex_main.htm для создания всех страниц текущего сайта. Расширение (или наследование) шаблона делается следующим образом (файл about.htm):

{% extends 'ex_main.htm' %} {% block title%}О сайте{% endblock %} {% block content %} h1>О сайте/h1> p>Классный сайт, если его доделать./p> {% endblock %}

Первой строчкой мы указываем базовый шаблон ‘ex_main.htm’, который собираемся расширять в шаблоне about.htm. Затем, указываем, что первый именованный блок title будет содержать строку «О сайте», а второй (content) – текст, помещенный в него.

Далее, в самой программе на Python мы делаем следующее:

from jinja2 import Environment, FileSystemLoader file_loader = FileSystemLoader('templates') env = Environment(loader=file_loader) template = env.get_template('about.htm') output = template.render() print(output)

Используя файловый загрузчик, берем файл шаблона about.htm из подкаталога templates и, затем, обрабатываем его с помощью метода render. На выходе получим следующую HTML-страницу:

DOCTYPE html> html> head> meta charset="UTF-8"> title>О сайте/title> /head> body> h1>О сайте/h1> p>Классный сайт, если его доделать./p> /body> /html>

Как видите, благодаря использованию механизма наследования шаблонов, легко и просто можно формировать страницы самого разного уровня сложности.

Разумеется, если базовый шаблон находится в другом каталоге относительно дочернего about.htm, то в инструкции extends это явно нужно прописать, например, так:

Будет взят шаблон default.tpl из подкаталога layout, находящийся в каталоге templates.

Далее, если нам нужно один и тот же блок использовать несколько раз на странице, то это делается так:

{% extends 'layout/default.tpl' %} {% block title%}О сайте{% endblock %} {% block content %} h1>{{ self.title() }}/h1> p>Классный сайт, если его доделать./p> {% endblock %}

Здесь self ссылается на объект текущего шаблона, в котором имеется метод title, т.к. существует блок с таким именем. И, далее, вызывая его, будет напечатано содержимое этого блока. Так можно делать с любым именованным блоком.

Ну а раз есть параметр self, то должен быть и super, который обращается к блоку базового шаблона и берет информацию непосредственно из него. В качестве простой демонстрации добавим в блок content базового шаблона строку «Блок контента». А в дочернем шаблоне about.htm сделаем вызов:

{% block content %} {{ super() }} h1>{{ self.title() }}/h1> p>Классный сайт, если его доделать./p> {% endblock %}

В итоге, получим результат:

Блок контента

О сайте

Классный сайт, если его доделать.

Если же вызов super убрать, то останутся только две строчки:

О сайте

Классный сайт, если его доделать.

Этот пример также показывает, что содержимое именованных блоков полностью замещается содержимым, указанным в дочерних шаблонах. И если нам нужно добавить новую информацию в производном шаблоне к информации блока базового шаблона, то следует вызывать метод super.

Вложенные блоки

При необходимости блоки можно вкладывать друг в друга создавая их иерархию. Например, пропишем в шаблоне базового класса в блоке content еще один блок – table_contents:

{% block content %} {% block table_contents %} ul> {% for li in list_table -%} li>{{li}}/li> {% endfor -%} /ul> {% endblock table_contents %} {% endblock content %}

Обратите внимание, здесь после endblock дополнительно указано какой блок заканчивается. Это необязательная запись, но она помогает лучше ориентироваться в сложных шаблонах. Далее, в дочернем шаблоне about.htm обратиться к этому вложенному блоку можно так:

{% block content %} {{ super() }} h1>{{ self.title() }}/h1> p>Классный сайт, если его доделать./p> {% endblock %}
{% block content %} {% block table_contents %}{{ super() }}{% endblock %} h1>{{ self.title() }}/h1> p>Классный сайт, если его доделать./p> {% endblock %}

Вот этот второй вариант более гибкий, т.к. в block content базового шаблона могут присутствовать и другие именованные блоки и мы здесь добавляем только один — table_contents, остальные будут проигнорированы. Если же убрать строчку с вложенным блоком:

{% block content %} h1>{{ self.title() }}/h1> p>Классный сайт, если его доделать./p> {% endblock %}

То получим страницу без оглавления. Видите, это очень удобно: если нужно добавляем буквально одной строчкой, а если не надо, то ничего не пишем.

Область видимости блоков

Давайте теперь, немного усовершенствуем базовый шаблон и добавим еще один блок для формирования элементов списка:

{% for li in list_table -%} li>{% block item %}{{ li }}{% endblock %}/li> {% endfor -%}

Если теперь выполнить программу, то внутри тегов li не будет никакой информации. Дело в том, что внутри блока item доступ к внешней переменной li нет. Чтобы исправить эту ситуацию и разрешить оперировать переменными из внешней области видимости, после имени блока следует прописать ключевое слово scoped:

li>{% block item scoped %}{{ li }}{% endblock %}/li>

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

{% block item %}p class="item">{{ super() }}/p>{% endblock %}

Смотрите, мы здесь воспользовались функцией super, чтобы получить текущее значение списка и дополнительно еще прописали тег p с соответствующим стилем оформления. При запуске увидим следующий результат:

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

Вложенное наследование шаблонов

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

Источник

Оцените статью