Шаблоны в PHP. Часть II.

Перед прочтением этой статьи рекомендуется сначала ознакомиться со статьёй Шаблоны в PHP. Часть I

Данная статья появилась после прочтения статьи ведущего разработчика и идеолога фреймворка Symfony, в которой автор криктикует "чистые" шаблоны на PHP. Очень хотелось бы разобрать рассуждения автора и понять, насколько он прав в утверждении, что...

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

Итак, пройдемся по пунктам из статьи Fabien Potencier:

Краткость

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

<?php echo $var ?>

Первое. Конструкцию <?php ?> подставляет любой PHP-редактор автоматически при написании первого симола "меньше" (<). Вам даже не нужно набирать руками эти 8 символов.

Второе:

Начиная с PHP 5.4 короткий тег echo <?= всегда распознается и действует, несмотря на значение опции short_open_tag — php.net

Так что любые разговоры связанные с тем, что в PHP исключат короткие теги уже можно, наконец, заканчивать. Теперь можно смело писать шаблоны с использованием коротких тегов:

<html>
<p>Привет, <?=$name?>! Тебе <?=$age?> лет.</p>
</html>

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

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

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

{{ var }}
{{ var|escape }}

Используя "чистую" PHP-шаблонизацию вам не обойтись без специфических для шаблона классов или функций. Описанная выше проблема многословности элементарно решается введением функции или метода в классе-помощнике. В моём фреймворке это сделано примерно так:

// Класс View представляет собой хранилище данных Вида и занимается
// преобразованием PHP-шаблонов посредством буферизации вывода.
class View {

    /**
     * Метод возвращает результат работы функции htmlspecialchars
     * с параметром ENT_QUOTES (преобразуются и двойные, и одиночные кавычки).
     *
     * @param string
     * @return string
     */
    public function e($in)
    {
        return htmlspecialchars($in, ENT_QUOTES);
    }

Соответственно в шаблоне все данные выводятся проходя через этот метод:

<html>
<p>Привет, <?=$this->e($this->name)?>! Тебе <?=$this->e($this->age)?> лет.</p>
</html>

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

Синтаксис ориентированный на шаблоны

По больше части это дело вкуса, но шаблонизаторы должны иметь красивые решения частых случаев. Для примера предположим, что необходимо отобразить массив и вывести текст по умолчанию, если этот массив пуст. Это очень распространенный случай, но версия на 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 "не очень читаема"?! Хоть убейте, но я не вижу здесь ничего не читаемого! Наоборот, мы явно видим логику отображения. Но самое интересное в том, что в данном конкретном примере выводится какой-то текст со звёздочкой в виде имитации списка, а не валидный HTML-список. Давайте перепишем данный пример правильно, добавив теги <ul> и <li>.

На PHP это будет выглядеть так:

<?php if ($items): ?>
   <ul>
   <?php foreach ($items as $item): ?>
      <li><?php echo $item ?></li>
   <?php endforeach; ?>
   </ul>
<?php else: ?>
    No item has been found.
<?php endif; ?>

Как в этом случае должна выглядеть версия на Django? Подозреваю, что как-то так:

{% if items %}
    <ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
{% else %}
      No item has been found.
{% endif %}

Как видно, разница крайне несущественна разницы никакой.

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

Я не говорю что PHP небезопасен. Но я хочу сказать что экранирование переменных в шаблоне это просто кошмар, как и продемонстрировал ранее:

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

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

<?php echo e($var) ?>

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

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