PHP-разработкой я занимаюсь уже довольно давно, и за это время научился использовать преимущества этого языка и избегать, по возможности, его недостатков. Но что мне никогда не нравилось в PHP — это встроенный механизм шаблонизации. Обилие символов “<?php … ?>” и многословных языковых конструкций бьет по глазам, возможность использования в шаблоне произвольного PHP-кода не способствует соблюдению принципа разделения логики и представления.
Поэтому я благодарен судьбе (и сообществу разработчиков, конечно) за то, что существуют альтернативные движки шаблонизации, с гораздо более приятным синтаксисом при тех же функциональных возможностях. Ну, а поскольку большая часть PHP-проектов у нас, в Центре Высоких Технологий, разрабатывается на Symfony2 Framework, то нашим любимым шаблонизатором стал Twig. Помимо указанных выше преимуществ, он еще и безгранично расширяемый, что очень часто помогает в работе.
Но жизнь частенько преподносит сюрпризы. Вот и на меня недавно свалился небольшой, но довольно интересный проект, делать который нужно было на… Битриксе! К счастью, работать с Битриксом мне уже приходилось, но было это давно(и неправда), поэтому я воспринял проект как возможность посмотреть на свой прошлый опыт с новой точки зрения, применить накопленные знания и навыки в несколько ином контексте.
И первое, что мне захотелось сделать — “прикрутить” Twig, чтобы не мучиться с нативной шаблонизацией.
Вот что из этого получилось.
К счастью, Битрикс позволяет использовать любой шаблонизатор вывода. Правда, только для шаблонов компонентов, шаблоны сайта все равно создаются на PHP. Для подключения шаблонизатора необходимо объявить глобальную функцию (да-да, это Битрикс, детка), которая будет осуществлять рендеринг шаблона. Функция может выглядеть, например, так:
Кроме того, функцию требуется зарегистрировать в глобальном массиве
В результате, если в каталоге шаблона компонента находится файл с именем template.twig, будет вызвана функция рендеринга
Как выяснилось, есть одна неприятная особенность: если в каталоге шаблона компонента одновременно находятся файлы template.twig и template.php, то использоваться будет PHP-шный шаблон. Следовательно, реализовать красивую неявную подмену типа шаблонов при подключении/отключении того или иного шаблонизатора не получится.
После того, как функция рендеринга зарегистрирована, остается проинициализировать и настроить сам движок. В случае Twig необходимо подключить к проекту его autoloader, указать путь к каталогу шаблонов и задать конфигурационные параметры (наиболее важные из них — использование отладочного режима и способ хранения кэша шаблонов). Также, при необходимости, можно добавить нужные расширения. Все это может выглядеть следующим образом:
Использование статичных методов и свойств класса в данном случае обусловлено архитектурой Битрикса: в нем нет механизма для размещения сервисных объектов, подобного, к примеру, контейнеру сервисов из Symfony2.
Работа по инициализации шаблонизатора выполняется в методе initialize(). Отмечу, что в нашем случае подключение Twig инкапсулировано в отдельном модуле Битрикса. Это, во-первых, дало нам возможность удобного использования функционала на разных проектах, а во-вторых, позволило задавать некоторые конфигурационные параметры через административный интерфейс CMS. В частности, отладочный режим включается в зависимости от значения опции debug_mode, управление которой вынесено на страницу настроек модуля в админке Битрикса.
Поскольку речь зашла о конфигурационных параметрах, то позволю себе сделать небольшое лирическое отступление. Принцип работы Twig заключается в следующем: при первом обращении к шаблону он компилируется в PHP-код, который затем исполняется при всех последующих обращениях. Файлы со сгенерированным кодом называются кэшем шаблонов и помещаются в каталог, указанный в опции cache. При изменении исходного кода шаблона, естественно, кэш нужно инвалидировать. Самый простой способ, который обычно применяется при релизе нового функционала — это полная очистка каталога кэша, которая реалиуется вызовом метода
Кстати, кэш шаблонов Twig никак не связан и не конфликтует с кэшем шаблонов Битрикса, поскольку в первом случае кэшируется PHP-код, а во втором — данные, полученные в результате работы компонента и HTML-разметка.
В контексте Битрикса также оказалось важным установить опцию autoescape в значение false, так как в функцию рендеринга передаются уже экранированные данные.
Вызов метода инициализации выполняется в файле подключения модуля:
Как видно из этого кода, путь к каталогу кэша также может быть сконфигурирован на странице настроек модуля.
Итак, шаблонизатор зарегистрирован и настроен, самое время начинать им пользоваться. И здесь, как обычно, не обошлось без подводных камней.
Во-первых, зачастую в шаблонах компонентов Битрикса приходится использовать некоторые битриксовые функции, а также глобальные объекты (что поделать, издержки архитектуры CMS). К счастью, Twig, как я уже отмечал, позволяет создавать собственные расширения, в которых можно описывать дополнительные теги, фильтры, функции и т.д. Поэтому было разработано
Затем, после долгих попыток понять, почему же в шаблон не передаются языковые константы, и последующего изучения кода ядра CMS, стало ясно, что языковой файл шаблона должен иметь точно такое же имя, что и сам шаблон, включая расширение. Это означает, что языковой файл шаблона template.twig должен также иметь имя template.twig, оставаясь при этом PHP-файлом! Что ж, странное поведение, но, как выяснилось, от разработчиков Битрикса можно еще и не такого ожидать.
Самым неприятным стало то, что при использовании Twig-шаблонов не отрабатывал component_epilog (завершающий этап рендеринга шаблона в Битриксе, позволяющий выполнить какие-либо действия независимо от того, закеширован шаблон или нет). Опять изучение кода ядра — и очередное изумление: component_epilog подключается только к нативным шаблонам! Более спорного решения в Битриксе, я еще, пожалуй, не встречал. Единственный доступный способ исправления данной ситуации — вручную вызывать component_epilog после рендеринга шаблона:
После проведенных доработок мы, наконец, получили действительно пригодное к использованию решение, которое упростило жизнь и мне (тот проект, с которого все и началось, был успешно реализован), и моим коллегам, которым тоже понравилась простота и лаконичность Twig.
И, конечно, мы не могли не поделиться результатом своих трудов. Модуль размещен в Bitrix Marketplace под забавным именем Твигрикс, он абсолютно бесплатен и доступен для скачивания всем интересующимся. А исходный код можно посмотреть на гитхабе. Мы от всей души надеемся, что Твигрикс немного украсит суровые будни суровых Битрикс-разработчиков.
Поэтому я благодарен судьбе (и сообществу разработчиков, конечно) за то, что существуют альтернативные движки шаблонизации, с гораздо более приятным синтаксисом при тех же функциональных возможностях. Ну, а поскольку большая часть PHP-проектов у нас, в Центре Высоких Технологий, разрабатывается на Symfony2 Framework, то нашим любимым шаблонизатором стал Twig. Помимо указанных выше преимуществ, он еще и безгранично расширяемый, что очень часто помогает в работе.
Но жизнь частенько преподносит сюрпризы. Вот и на меня недавно свалился небольшой, но довольно интересный проект, делать который нужно было на… Битриксе! К счастью, работать с Битриксом мне уже приходилось, но было это давно
И первое, что мне захотелось сделать — “прикрутить” Twig, чтобы не мучиться с нативной шаблонизацией.
Вот что из этого получилось.
К счастью, Битрикс позволяет использовать любой шаблонизатор вывода. Правда, только для шаблонов компонентов, шаблоны сайта все равно создаются на PHP. Для подключения шаблонизатора необходимо объявить глобальную функцию (да-да, это Битрикс, детка), которая будет осуществлять рендеринг шаблона. Функция может выглядеть, например, так:
function renderTwigTemplate($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
{
echo TwigTemplateEngine::renderTemplate($templateFile, array(
'params' => $arParams,
'result' => $arResult,
'langMessages' => $arLangMessages,
'template' => $template,
'templateFolder' => $templateFolder,
'parentTemplateFolder' => $parentTemplateFolder,
));
}
Кроме того, функцию требуется зарегистрировать в глобальном массиве
$arCustomTemplateEngines
с указанием расширения файла шаблона:global $arCustomTemplateEngines;
$arCustomTemplateEngines["twig"] = array(
"templateExt" => array("twig"),
"function" => "renderTwigTemplate"
);
В результате, если в каталоге шаблона компонента находится файл с именем template.twig, будет вызвана функция рендеринга
renderTwigTemplate()
, на вход которой будут переданы все необходимые данные: имя и путь к файлу шаблона, параметры вызова компонента, результат выполнения компонента, а также языковые константы для данного шаблона.Как выяснилось, есть одна неприятная особенность: если в каталоге шаблона компонента одновременно находятся файлы template.twig и template.php, то использоваться будет PHP-шный шаблон. Следовательно, реализовать красивую неявную подмену типа шаблонов при подключении/отключении того или иного шаблонизатора не получится.
После того, как функция рендеринга зарегистрирована, остается проинициализировать и настроить сам движок. В случае Twig необходимо подключить к проекту его autoloader, указать путь к каталогу шаблонов и задать конфигурационные параметры (наиболее важные из них — использование отладочного режима и способ хранения кэша шаблонов). Также, при необходимости, можно добавить нужные расширения. Все это может выглядеть следующим образом:
class TwigTemplateEngine
{
private static $twigEnvironment;
public static function initialize($templateRootPath, $cacheStoragePath)
{
Twig_Autoloader::register();
$debugModeOptionValue = COption::GetOptionString("htc.twigintegrationmodule", "debug_mode");
$debugMode = ($debugModeOptionValue == "Y") ? true : false;
$loader = new Twig_Loader_Filesystem($templateRootPath);
self::$twigEnvironment = new Twig_Environment($loader, array(
'autoescape' => false,
'cache' => $cacheStoragePath,
'debug' => $debugMode
));
self::addExtensions();
global $arCustomTemplateEngines;
$arCustomTemplateEngines["twig"] = array(
"templateExt" => array("twig"),
"function" => "renderTwigTemplate"
);
}
private static function addExtensions()
{
self::$twigEnvironment->addExtension(new Twig_Extension_Debug());
self::$twigEnvironment->addExtension(new BitrixTwigExtension());
}
public static function renderTemplate($templateFile, array $context)
{
return self::$twigEnvironment->render($templateFile, $context);
}
public static function clearCacheFiles()
{
self::$twigEnvironment->clearCacheFiles();
}
}
Использование статичных методов и свойств класса в данном случае обусловлено архитектурой Битрикса: в нем нет механизма для размещения сервисных объектов, подобного, к примеру, контейнеру сервисов из Symfony2.
Работа по инициализации шаблонизатора выполняется в методе initialize(). Отмечу, что в нашем случае подключение Twig инкапсулировано в отдельном модуле Битрикса. Это, во-первых, дало нам возможность удобного использования функционала на разных проектах, а во-вторых, позволило задавать некоторые конфигурационные параметры через административный интерфейс CMS. В частности, отладочный режим включается в зависимости от значения опции debug_mode, управление которой вынесено на страницу настроек модуля в админке Битрикса.
Поскольку речь зашла о конфигурационных параметрах, то позволю себе сделать небольшое лирическое отступление. Принцип работы Twig заключается в следующем: при первом обращении к шаблону он компилируется в PHP-код, который затем исполняется при всех последующих обращениях. Файлы со сгенерированным кодом называются кэшем шаблонов и помещаются в каталог, указанный в опции cache. При изменении исходного кода шаблона, естественно, кэш нужно инвалидировать. Самый простой способ, который обычно применяется при релизе нового функционала — это полная очистка каталога кэша, которая реалиуется вызовом метода
Twig_Environment::clearCacheFiles()
(в нашем модуле реализована обертка для этого метода, позволяющая очищать кэш по нажатию кнопки в административном интерфейсе). Кроме того, Twig умеет автоматически пересоздавать кэш конкретного шаблона при изменении его исходного кода: для этого необходимо установить опцию auto_reload в значение true. Но обычно такой подход требуется только в режиме разработки, поэтому вместо auto_reload можно установить опцию debug, что даст такой же эффект при работе с кэшем, а также позволит использовать отладочные возможности Twig. Кстати, кэш шаблонов Twig никак не связан и не конфликтует с кэшем шаблонов Битрикса, поскольку в первом случае кэшируется PHP-код, а во втором — данные, полученные в результате работы компонента и HTML-разметка.
В контексте Битрикса также оказалось важным установить опцию autoescape в значение false, так как в функцию рендеринга передаются уже экранированные данные.
Вызов метода инициализации выполняется в файле подключения модуля:
CModule::AddAutoloadClasses(
'htc.twigintegrationmodule',
array(
'TwigTemplateEngine' => 'classes/general/templating/TwigTemplateEngine.php',
'BitrixTwigExtension' => 'classes/general/templating/BitrixTwigExtension.php',
'Twig_Autoloader' => 'vendor/Twig/Autoloader.php',
)
);
// Initialize Twig template engine
$documentRoot = $_SERVER['DOCUMENT_ROOT'];
$cacheStoragePathOption = COption::GetOptionString("htc.twigintegrationmodule", "cache_storage_path");
if ($cacheStoragePathOption == "") {
$cacheStoragePath = $documentRoot . BX_PERSONAL_ROOT . "/cache/twig";
} else {
$cacheStoragePath = $documentRoot . $cacheStoragePathOption;
}
TwigTemplateEngine::initialize($documentRoot, $cacheStoragePath);
Как видно из этого кода, путь к каталогу кэша также может быть сконфигурирован на странице настроек модуля.
Итак, шаблонизатор зарегистрирован и настроен, самое время начинать им пользоваться. И здесь, как обычно, не обошлось без подводных камней.
Во-первых, зачастую в шаблонах компонентов Битрикса приходится использовать некоторые битриксовые функции, а также глобальные объекты (что поделать, издержки архитектуры CMS). К счастью, Twig, как я уже отмечал, позволяет создавать собственные расширения, в которых можно описывать дополнительные теги, фильтры, функции и т.д. Поэтому было разработано
небольшое расширени
е BitrixTwigExtension, предоставляющее доступ к API Битрикса в шаблонах. При этом мы постарались оставить доступным минимальный набор API, чтобы оградить разработчиков от желания реализовывать бизнес-логику в шаблонах. Затем, после долгих попыток понять, почему же в шаблон не передаются языковые константы, и последующего изучения кода ядра CMS, стало ясно, что языковой файл шаблона должен иметь точно такое же имя, что и сам шаблон, включая расширение. Это означает, что языковой файл шаблона template.twig должен также иметь имя template.twig, оставаясь при этом PHP-файлом! Что ж, странное поведение, но, как выяснилось, от разработчиков Битрикса можно еще и не такого ожидать.
Самым неприятным стало то, что при использовании Twig-шаблонов не отрабатывал component_epilog (завершающий этап рендеринга шаблона в Битриксе, позволяющий выполнить какие-либо действия независимо от того, закеширован шаблон или нет). Опять изучение кода ядра — и очередное изумление: component_epilog подключается только к нативным шаблонам! Более спорного решения в Битриксе, я еще, пожалуй, не встречал. Единственный доступный способ исправления данной ситуации — вручную вызывать component_epilog после рендеринга шаблона:
function renderTwigTemplate($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
{
echo TwigTemplateEngine::renderTemplate($templateFile, array(
'params' => $arParams,
'result' => $arResult,
'langMessages' => $arLangMessages,
'template' => $template,
'templateFolder' => $templateFolder,
'parentTemplateFolder' => $parentTemplateFolder,
));
$component_epilog = $templateFolder . "/component_epilog.php";
if(file_exists($_SERVER["DOCUMENT_ROOT"].$component_epilog))
{
$component = $template->__component;
$component->SetTemplateEpilog(array(
"epilogFile" => $component_epilog,
"templateName" => $template->__name,
"templateFile" => $template->__file,
"templateFolder" => $template->__folder,
"templateData" => false,
));
}
}
После проведенных доработок мы, наконец, получили действительно пригодное к использованию решение, которое упростило жизнь и мне (тот проект, с которого все и началось, был успешно реализован), и моим коллегам, которым тоже понравилась простота и лаконичность Twig.
И, конечно, мы не могли не поделиться результатом своих трудов. Модуль размещен в Bitrix Marketplace под забавным именем Твигрикс, он абсолютно бесплатен и доступен для скачивания всем интересующимся. А исходный код можно посмотреть на гитхабе. Мы от всей души надеемся, что Твигрикс немного украсит суровые будни суровых Битрикс-разработчиков.