Дружим Битрикс и Twig

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

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

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

Почему, собственно, Twig

А собственно, хз 🙂 Вообще говоря, я поначалу приглядывался к Jade. Синтаксически очень красив, лаконичен. Но его реализация для php развивается достаточно медленно, поэтому пришлось отбросить этот вариант.

На заре своей карьеры сталкивался со Smarty, остались неприятные впечатления от него (скорее всего из-за недостатка опыта). Наверно, имеет смысл взглянуть на него по новой, но пока не до этого.

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

В пользу Twig был Symfony 2 и Drupal 8, плюс нативная поддержка его в phpStorm, несложное подключение и синтаксис, который быстро запоминается (что плюс для новичков).

Задача

Мне нужен модуль, который я смог бы с помощью composer по быстрому подрубить к проекту и использовать в битриксе. Быстрый гуглёж дал мне уже готовую реализацию модуля твигрикс, и даже статью на хабре, в которой описывается процесс созидания такого модуля и подключения функционала шаблонизатора.
Однако, оно мне не подошло по нескольким причинам — его нельзя подцепить через composer, т.к. его исходники хранятся в виде обновлений для marketplace, причем в windows-1251 кодировке, наследование шаблонов там не реализовано в полной мере, а также в поставку модуля входит Twig, который нужно обновлять, в моем случае его будем выносить под контроль composer.

Делаем библиотеку

Сказано — сделано. От создания модуля для битрикс я сразу отказался, т.к. битрикс позволяет администратору сайта легко отрубать модуль, а в мои планы не входит возможность отключения этого функционала через админку. Поэтому просто сделаем composer библиотеку.

Давайте разбираться дальше. Первая засада нас подстерегает сразу же. Я уже писал в ранее, что битрикс реализовывает фичи «как то не до конца». Вот и шаблонизаторы он поддерживает, но только в компонентах 2.0. Шаблоны сайтов, статическое содержимое остаются не у дел. Ну что же — надо с чего то начинать, вот и начал подключать к компонентам.

В основе всей идеи интеграции шаблонизатора в битриксовые компоненты лежит создание глобальной функции (Битрикс, обожаю тебя!). Эта функция будет принимать на вход данные, с которыми работает шаблон (arResult, arParams, arLang, путь до файла шаблона и еще кое-что), и в результате работы этой функции она должна вывести в стандартный поток отрендеренное содержимое шаблона. При рендеринге шаблона битрикс проверяет, нет ли такой функции, и если она есть, то управление рендерингом передается ей.

Для начала нужно зарегистировать такую функцию. Сделать это можно с помощью глобального массива (снова обожаю тебя, Битрикс!) $arCustomTemplateEngines. Каждая функция рендеринга соответствует конкретному расширению файла. Делаем так, чтобы при рендеринге учитывалось расширение .twig

Регистрация выглядит следующим образом:

Разместить этот код нужно где-то до того момента, как будут выполнены первые компоненты. Теперь нужно эту функцию реализовать.

Эту функцию нужно разместить там, где она будет доступна для вызова всеми модулями системы. В моем случае я использую автозагрузку composer для файлов. К сожалению, нельзя использовать callable-тип для функции, т.к. ядро проверяет именно наличие функции по ее имени. Исправление пары строчек в ядре позволило бы использовать callable и избавиться от этой глобальной функции, но, увы, не получится (по крайней мере пока).

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

Создаем класс, который будет хранить инстанс нашего Twig движка. Напомню, что Twig мы решили подключать через composer, поэтому примем во внимание, что если библиотека установлена, значит и ее зависимости уже тоже установлены, а автолоадер композера уже подключен. Таким образом, создаем класс:

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

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

 

Удобное наследование

Чтобы использовать наследование, нужно в шаблоне использовать управляющую конструкцию

где template_name — это некое имя шаблона. В него можно вписать полный или относительный путь до шаблона, от которого хотите унаследоваться, но это жутко неудобно, особенно в случае с битриксом (когда шаблон компонента с одним названием может храниться аж в 4х местах).

Чтобы побороть эту путаницу в Twig есть возможность создать свой загрузчик для шаблонов, чтобы обучать загружать шаблоны не по их пути, а по специальному имени. Для этого создан интерфейс \Twig_Loader_Interface, который нужно реализовать в виде класса и скормить его конструктору Twig_Environment.

Я решил использовать такой синтаксис, по-моему вполне удобно и удачно:

vendor:component_name[:template[:specific_template_file]

  • vendor — это пространство имен разработчика, например bitrix или maximaster
  • component_name — имя компонента, шаблон которого наследуется
  • template — имя шаблона, который нужно унаследовать. Необязательный, по-умолчанию .default
  • specific_template_file — конкретный файл шаблона (без расширения). Необязательный, по-умолчанию template

Например, вы хотите унаследовать шаблон new-year компонента maximaster:product. Для этого в шаблоне twig нужно написать

Красота!

После этого мне понадобилось добавить возможность обращения к некоторым дефолтным функциям, переменным, константам битрикса и php, чтобы не потерять в функциональности. Для этого создаем расширение для Twig. Расширение создается также просто — нужно всего лишь создать класс наследник от Twig_Extension и зарегистрировать его при инициализации Twig_Environment.

Осталось добавить только пару штрихов по управлению конфигами и вуаля — библиотека готова, можно скачать на github.

Полный код TemplateEngine::getInstance получился таким:

Дальше будем думать, как подружить его с шаблонами сайтов и статикой.

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

Спасибо за внимание.

  • Anton Art

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

    Должно быть
    https://paramountcomedy.ru/video/
    Ошибка:
    http://test.paramountcomedy.artantme.ru/video/

    Заранее спасибо!

    • Наличие прав доступа для записи в указанную директорию проверили?

      • Anton Art

        С правами было все ок, а вот в конфиге твига остались хвосты настроек от старого сервера.

        Спасибо, разобрались!

  • Ilya Nekrasov

    Где то полгода назад делал тоже самое только для любимого блейда https://github.com/arrilot/bitrix-blade тоже получил кучу удовольствия от описанных мест. Что насчет кэша — то у меня он хранится просто в /bitrix/cache/blade и дальше уже при деплоев можно из этой папки удалять как угодно. При общем сбрасывании кэша сайта он оттуда прекрасно тоже удаляется

    • О! Илья, спасибо за ответ. Я слежу за вашими работами на гитхабе 🙂 Чувствуется, что вы многое заимствуете из Laravel, и это хорошо. Чем больше таких людей как Вы, тем приятнее становится работать с Битриксом.
      Насчет очистки кеша — сейчас данный модуль также хранит весь свой кеш в отдельной директории, и ее тоже можно без особых проблем сбросить как при полной очистке через админку, так и при деплое. Но в большинстве случаев не нужно сносить эту директорию целиком.
      Сейчас этот модуль можно использовать совместно с битриксовым кешем, полагаясь целиком на него. Не знаю, как blade, но twig кеширует шаблон в обычный php класс, который потом идет на исполнение. Можно заставить twig каждый раз проверять разницу между файлом шаблона и классом (по времени изменения) и изменять класс только при изменении файла. Если поверх этого механизма работает обычный кеш компонентов битрикса, то можно просто включить слежение за изменением шаблона и все будет работать штатно. Но все равно это как-то не совсем то, хочется добиться полной интеграции в систему