Перевод: Шаблонизаторы в PHP

:

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

-----------------------------------------------------------------------
Итак, вы считаете что PHP это уже шаблонизатор? Так думал и я… и очень долго. Но недавно я передумал. Несмотря на то что PHP может использоваться как шаблонизатор, его синтаксис ужасен для этих целей.

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

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

И когда я несколько дней назад спросил о лучших и популярных шаблонизаторах на PHP в Твиттере, некоторые, естественно, ответили «сам PHP». Это было вполне предсказуемо, ведь несколько недель назад я бы ответил так же.

Так почему же PHP (уже) не является удобным шаблонизатором?

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

Шаблонизаторы стремительно развивались начиная с 1995 года, когда вышел первый релиз PHP/FI:

<!--include /text/header.html-->
 
<!--getenv HTTP_USER_AGENT-->
<!--ifsubstr $exec_result Mozilla-->
  Hey, you are using Netscape!<p>
<!--endif-->
 
<!--sql database select * from table where user='$username'-->
<!--ifless $numentries 1-->
  Sorry, that record does not exist<p>
<!--endif exit-->
  Welcome <!--$user-->!<p>
  You have <!--$index:0--> credits left in your account.<p>
 
<!--include /text/footer.html-->

И значимым фактом является то, что PHP не поддерживает множество возможностей современных шалонизаторов.

Я буду использовать Django как пример современного шаблонизатора в своих примерах по причинам которые вы поймете позже.

Ниже описано то что я хочу видеть в современном шаблонизаторе:

Краткость

PHP многословен. Только для того чтобы просто вывести переменную необходимо не менее 14 символов (нет, использование более компактного <?= непреемлемо):
<?php echo $var ?>

И PHP становиться нелепо многословен при необходимости экранировать вывод (да, экранирование данных полученных из небезопасного источника является обязательным)

<?php  echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>

Сравнити это с двумя примерами написанными на Django:

{{ var }}
{{ var|escape }}
Синтаксис ориентированный на шаблоны

По больше части это дело вкуса, но шаблонизаторы должны иметь красивые решения частых случаев. Для примера предположим, что необходимо отобразить массив и вывести текст по умолчанию, если этот массив пуст. Это очень распространенный случай, но версия на PHP не очень читаема:
<?php if ($items): ?>
   <?php foreach ($items as $item): ?>
    * <?php echo $item ?>
  <?php endforeach; ?>
<?php else: ?>
    No item has been found.
<?php endif; ?>

Версия на Django гораздо лучше — спасибо конструкции else для тега for:

{% for item in items %}
  * {{ item }}
{% else %}
  No item has been found.
{% endfor %}
Повторное использование

PHP развивался с учетом возможности повторного использования кода. Начиная с 5-й версии реализация объектов сделала шаг вперед и, после того как в следующей версии будут поддерживаться типажи, мы получим язык программирования удовлетворяющий наши требования. Я действительно рад всем этим изменения т.к. это помогает мне писать программы лучше, но это не имеет значения когда все что тебе нужно — это написать шаблон.

В Django ввели наследование шаблонов несколько лет назад, пытаясь повторить наследование классов в шаблонах:

<!-- base.html -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    {% block head %}
      <link rel="stylesheet" href="main.css" />

    {% endblock %}
  </head>
  <body>
    {% block content %}{% endblock %}
  </body>
</html>
 
<!-- index.html -->
{% extends "base.html" %}

 
{% block head %}
  {{ block.super }}
  <link rel="stylesheet" href="main.css" />
{% endblock %}
 
{% block content %}
  Index content
{% endblock %}

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

Безопасность

Я не говорю что PHP небезопасен. Но я хочу сказать что экранирование переменных в шаблоне это просто кошмар, как и продемонстрировал ранее:
<?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>

Конечно, вы можете написать свою функции и сделать ее котороче, но это не то о чем я говорю:

<?php echo e($var) ?>

Я считаю что безопасность должа быть на уровне по умолчанию, особенно для шаблонов, которые зачастую пишут не программисты, которые не обращают внимания на возможные XSS и CSRF уязвимости.

Насколько я знаю, Symfony был самым первым фреймворком поддерживающим автоматическое экранирование в шаблонах (2006); и все основные фреймворки пошли тем же путем: в Django по умолчанию включено экранирование начиная с версии 1.0, и в Ruby on Rails это тоже появится с версии 3.

Включенное автоматическое экранирование так же означает что аудировать(?) приложение гораздо проще. Взгляните на примеры с отключенным экранированием:

{% autoescape off %}
  {{ object.as_html }}
{% endautoescape %}
 
{{ object.as_html|safe }}
 
Конечно, я в курсе возможных проблем с автоматическим экранированием. Вам все так же нужно быть внимательными при экранировании переменных в JavaScript, но это гораздо проще чем экранировать абсолютно все вручную.
Режим «Песочницы»

Эта возможность особенно необходима в том случае когда пользователи могу сами редактировать шаблоны сайта (например, из системы управления сайтом). Это не является необходимость, однако бывает важно. Исполнение шаблонов в «песочнице» означает возможность ограничить то что может быть выполенно: методы/функции которые можно вызывать, теги которые можно использовать и т. д.

Ни у PHP, ни у Django нет такого режима, так что продолжайте читать эту статью для дальнейшего объяснения этой секции.

Альтернативные шаблонизаторы для PHP


Итак я приступил к поиску шаблонизатора который имел бы все возможности упомянутые выше. Я нашел много разных шаблонизаторов, но ни один из них не удовлетворил все мои нужды. Следующий раздел о некоторых из них, среди которых есть и рекомендованные мне ответами в Твиттер.
На PHP написан миллион шаблонизаторов, я тестировал только самые «популярные». И т. к. я их не использую мной могут быть допущены ошибки использования того или иного шаблонизатора.
Smarty и Smarty 3

Это то что первым пришло в голову. И сам язык шаблонов Django был «навеян» Smarty. Smarty это стандарт де-факто для PHP.
Hello {$name|escape}
{section name=item loop=$items}
  {$items[item]|escape}
{/section}

Но у Smarty есть ряд проблем:

  • Не объектно-ориентирован
  • Нет наследования шаблонов
  • Нет «песочницы»
  • Нет автоматического экранирования

Насколько я понимаю, Smarty 3 вот-вот выйдет и достаточно сильно улучшит состояние:
  • Объектно-ориентированная архитектура
  • Автоматическое экранирование
  • Наследование шаблонов

Я протестировал обе версии, но скорость работы достаточно низкая (в конце имеется информация о тестах)
PHPTAL

PHPTAL очень приятный проект, использующий синтаксис шаблонов Zope. Очень хорошо спроектирован, имеет большое количество возможностей, но не умеет работать с не-HTML шаблонами, что может стать проблемой, если вы хотите использовать один язык шаблонов также для писем и RSS лент.
<?xml version="1.0"?>
<html>
  <body>
    Hello <span tal:content="name" />
    <ul tal:repeat="item items">
      <li tal:content="item"></li>
    </ul>
  </body>
</html>

Еще я думаю что синтаксис может оказаться непонятным веб-дизайнерам, особенно при использовании расширенных возможностей, таких как наследование шаблонов:

<html metal:use-macro="layout.xml/main">
 <body metal:fill-slot="content">
   Hello <span tal:content="name" />
   <ul tal:repeat="a array">
     <li tal:content="a"></li>
   </ul>
 </body>
</html>
<html metal:define-macro="main">
  <metal:block define-slot="content"/>
</html>
 

Одним из важных достоинств можно назвать автодополнение в среде разработки и гарантировано «правильно сформированный» HTML код.
eZ Components Templates

Компонент шаблонов в составе eZ Components так же предоставляет очень удачный языком шаблонизатора. Здесь присутвуют почти все возможности шаблонизаторов… даже чресчур, если вы хотите знать мое мнение:
{use $name}
{use $items}
Hello {$name}
{foreach $items as $item}
 * {$item}
{/foreach}

Наследование шаблонов не поддерживается, и меня беспокоит скорость работы. Это самая медленная библиотека что я тестировал, и сильно медленне остальных.
Dwoo

Dwoo — это, прежде всего, интересный проект. Он позиционирует себя как альтернативу Smarty. И их работа стоит внимания:
<html>
  <body>
    {block "content"}{/block}
  </body>
</html>
{extends "layout.tpl"}
 
{block "content"}
  {include("basic.tpl")}
{/block}

Dwoo подражает Smarty, но также вносит и свои дополнения, например, наследование шаблонов, и работает гораздо быстрее Smarty.

К сожалению, у Dwoo нет «песочницы» и ядро библиотеки недостаточно гибкое.

Calypso

Calypso это реализация шаблонизатора Django на PHP. Я упоминаю о нем т.к. он клон Django и на него ссылались в Твиттере. Но сам автор считает что его реализация неудачна.
Twig

Когда я приступил к поиску PHP шаблонизатора, я сосредоточился на библиотеках копирующих поведение Django. Спустя несколько часов «гугления» был найден Twig. Его автором является Armin Ronacher, известный по проекту of Jinja (шаблонизатор для Python). Несомненно я испытываю крайнее уважение к Армину за его замечательную работу над Jinja. Twig скорее похож на Jinja, чем на Django, как описано в реализации.

Он написал Twig в 2008 году для платформы блогов Chypr. Но больше не возвращался к разработке и больше занимался разработкой на Python.

Когда я взглянул на код, я сразу понял что это то что я ищу. Главное отличие от Calypso в том что Twig компилирует шаблоны в обычный PHP-код. Я начал использовать эту библиотеку и в конце этой недели спросил у Армина не желает ли он дать своему проекту новую жизнь. Его ответ был полон энтузиазма, и я приступил к изучению кода. Моя версия сильно отличается от версии Армина, но «лексер» и «парсер» практически оригинальные.

Я потратил всего несколько дней на работу над кодом, но я уже горжусь результатом и думаю что пора показывать библиотеку публично. Вчера я написал документацию и сделал простой сайт. Осталось еще много работы: закончить документацию, добавить тесты и PHPDoc; но код уже целостен и функционален:

  • Встроенное наследование шаблонов (шаблоны компилируются как классы)
  • Автоматическое экранирование (отсутствуе дополнительное время на запуск — все делается во время компиляции)
  • Очень безопасный режим «песочницы» (список допустимых тегов, фильтров и методов которые разрешены в шаблоне)
  • Расширяемость: вы можете переписавать все что угодно, даже функции ядра, написав расширение; так же можно маниаулировать AST (Abstract Syntax Tree) (?) перед компиляцией. Используя эти возможности вы можете создат даже свой собственны язык — DSL (Domain Specific Language), ориентированный на ваше приложение.

Несмотря на то что Twig самый функциональный шаблонизатор, он еще и самый быстрый.
Библиотека Время (сек) Память (Кб) Шаблонов в секунду
Twig 3 1 190 3 333
PHPTAL 3.8 2 100 2 632
Dwoo 6.9 1 870 1 449
Smarty 2 12.9 2 350 775
Smarty 3 14.9 3 230 671
Calypso 34.3 620 292
eZ Templates 53 5 850 189

Я тестировал простые шаблоны содержащие 1 вывод и итерацию трех элементов, декорируя простым макетом. Время — среднее для 10 запусков; состоящее из 1 компиляции и 10 000 отображений (рендеринга). Для библиотек, не поддерживающих наследование был использован шаблон подключающий шапку (header) и подвал (footer) отдельно и, в библиотеках не поддерживающих автоматического экранирования, экранирование было выполнено вручную.

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

Библиотека Память без компиляции (Кб)
Twig 383
PHPTAL 598
Dwoo 1 645
Smarty 2 1 634
Smarty 3 1 790
Calypso 614
eZ Templates 2 783

Если вы хотите узнать больше, то посетите сайт, также можете принять участие в обсуждении или стать одним из разработчиков подписавшись на список рассылок для разработчиков.

-----------------------------------------------------------------------
Это мой первый перевод. Не только для хабра, а вообще. Поэтому приветствуются комментарии не только про шаблонизаторы и Twig, но и про сам перевод.

Перевод: Подведение итогов.