Знакомьтесь, шаблонизатор MODX
В сообществе периодически возникают вопросы о шаблонизаторах, проблемах с ними и т.п. Ответить на них поможет понимание того, как работает механизм шаблонизации в MODX. Давайте попробуем вместе по винтикам разобрать принцип парсинга контента ресурса. Конечно, тут больше подошёл бы видео формат. Но я сейчас на самоизоляции далеко от своего рабочего компьютера, поэтому мои возможности ограничены. И так, приступим.
Шаблонизация в MODX
Структура формирования HTML ответа в MODX схожа с другими шаблонизаторами — HTML шаблон + контент. Но примерение сильно отличается. Виной тому совершенно другой подход к парсингу содержимого. В MODX нет строгих правил что нужно писать в шаблоне, а что в контенте ресурса. Часто встречаются ситуации, когда контент содержит не только текст, но и различные элементы (сниппеты, чанки). А бывает и элементы логики вставляют. Достаточно трудно представить себе такое, скажем, в Laravel. Даже скажем по другому — такое невозможно!
В MODX решили не использовать готовые шаблонизаторы, а придумали свой.
Тут надо оговориться, что для админки используется не встроенный шаблонизатор, а Smarty.
И в этом есть своя логика. Разработчики MODX придумали более простой синтаксис, который облегчает жизнь непрограммистам. Они ввели несколько типов тегов, понятных и простых в использовании. Вместо кодирования на php — сниппеты с параметрами. Для удобного структурирования и многоразового использования — чанки. А ещё плейсхолдеры лексиконов и системных настроек. И всё это увязано с возможностью управления кэшированием (через !). А фильтры и наборы параметров добавляют дополнительные возможности.
Если представить, что MODX всё-таки CMS(!) и сфера его применения — небольшие и средние сайты, то данный шаблонизатор, не смотря на свои ограниченные возможности, легко справится со всем поставленными задачами. Наверняка найдутся люди, которые не согласятся с таким определением MODX. Хочу им сказать, что есть много факторов, почему я так позиционирую MODX — начиная от архитектурных ограничений, элементов, хранящихся в БД и заканчивая отсутствием развития. Но вернёмся к теме.
Но кроме всех этих вещей они ещё и придумали свою логику парсинга контента — многократный и рекурсивный. Что совершенно меняет подход к вёрстке и размывает границу между HTML шаблоном и содержанием ресурса. И как следствие — шаблон страницы не компилируется в готовый php скрипт, который затем выполняется один раз и очень быстро, а каждый раз ищет в тексте теги MODX и рекурсивно парсит их. Первый раз все, а затем только некэшированные. Как вы понимаете, это сказывается на времени обработки. А в случае с подключённым Fenom не сильно спасает и кэшированный вызов. При данном подходе крайне сложно создать полноценный шаблонизатор. Но для обозначенных целей его функциональности вполне хватает.
Парсер MODX
Давайте теперь перейдём непосредственно к механике парсинга документа. Условие следующее — у нас есть документ с шаблоном, у которого нет кэша. Документ хранит какую-то инфморацию. Например, статью.
Подготовка HTML ответа происходит в три этапа:
- парсинг кэшируемых тегов;
- парсинг некэшируемых тегов без удаления нераспарсенных;
- парсинг некэшируемых тегов с удалением всех нераспарсенных;
Парсинг кэшируемых тегов
Первым делом шаблонизатор формирует структуру ответа. Он подгружает из базы данных шаблон, берёт его содержание и парсит только кэшируемые теги в нём. И самый важный тег, который связывает шаблон с документом — [[*content]]
. Этим занимается непосредственно парсер:
$maxIterations= intval($this->xpdo->getOption('parser_max_iterations',10)); // Третий параметр указывает, что нужно обрабатывать только кэшируемые теги. $this->xpdo->parser->processElementTags('', $this->_content, false, false, '[[', ']]', array(), $maxIterations);
Обратите внимание на последний параметр. Он отвечает за глубину рекурсии. Т.е. сколько раз парсить контент. Первая итерация — парсинг содержимого шаблона. Дальше идёт проверка полученного результата каждого тега на наличие других кэшируемых тегов. Например, если тег [[*content]]
, который содержит контент ресурса, вернёт кэшируемые теги, то парсер запустит вторую итерацию. Если распарсенные теги опять вернут кэшируемые теги, то запуститься следующая итерация. И так до тех пор, пока в ответах перестанут возвращаться кэшируемые теги. Но максимум указанное количество раз. В данном случае 10.
На данном этапе нужно знать пару моментов
- Если парсер не смог распарсить тег, то этот тег возвращается в контент.
- В параметрах кэшируемых элементов (сниппетов, чанков) нельзя указать некэшируемый тег.
Надо отметить, что парсер собирает все теги MODX — и кэшируемые и не очень. Просто в процессе обработки некэшируемые теги исключаются. Но всё равно на обработку этих тегов тратится время.
Парсинг некэшируемых тегов
Данная операция делится на 2 этапа:
- Парсинг без удаления нераспарсенных тегов.
- Парсинг с удалением нераспарсенных тегов.
Они отрабатывают один за другим
// За удаление тегов отвечает 4-й параметр. $this->xpdo->parser->processElementTags('', $this->_content, true, false, '[[', ']]', array(), $maxIterations); $this->xpdo->parser->processElementTags('', $this->_content, true, true, '[[', ']]', array(), $maxIterations);
Глядя на этот код возникает вопрос — нафига делать 2 вызова, если можно сделать только второй, но удвоить глубину рекурсии? У меня нет точного ответа, но есть предположение. Если вдруг остались нераспарсенные теги плейсхолдеров типа [[+var]]
, которые появятся на втором этапе, например, их определит сниппет, то на третьем этапе, когда парсер будет обрабатывать нераспарсенные теги, они обработаются. Если после этого какие-то теги останутся, то они будут удалены из контента. Но, если честно, предположение так себе.
Парсинг параметров элементов
Отдельно хочу отметить процедуру обработки параметров элементов. Иногда в параметрах сниппета указывают какой-нибудь плейсхолдер:
[[!snippet? user=`[[+id]]`]]
Понятно, что он должен быть распарсен до того, как отработает сниппет. Для этого парсер выделяет из тега часть строки с параметрами (после знача ?) и парсит её перед вызовом сниппета.
Заключение
Из вышеописанного следует, что на втором и третьем этапе кэшируемые теги также будут парсится. И тут возможна такая ситуация, когды вы указали тег несуществующего чанка [[$chunk]]
. В этом случае парсер будет делать запрос в БД для загрузки указанного чанка на каждом этапе и в конце концов его удалит. Но эта процедура занимает время и отрицательно влияет на производительность.
Сторонние шаблонизаторы в MODX
Ну и напоследок рассмотрим, как в MODX встраивается, например, шаблонизатор Fenom. Подключается он с помощью специального класса парсера pdoParser
, в котором прописывается вся необходимая логика. Если мы заглянем в него, то увидим, что обработка тегов фенома начинается только на втором этапе (обработка некэшируемых тегов). Причем оба шаблонизатора (и MODX и Fenom) работают совместно. Только сначала парсятся теги Fenom, а затем теги MODX.
Но на парсинге работа не кончается. Дальше парсер MODX заменяет кэшированные теги на содержимое. А также записывает их в массив кэша ресурса на тот случай, если этот тег встретится дальше. Таким образом, при повторном запросе страницы их в контенте уже не будет. Что положительно влияет на скорость загрузки. При условии, что у документа стоит галочка «Кэшировать». Но стандартный парсер не умеет так работать с кэшированными тегами Fenom. И тут включается другой механизм кэширования — программный. За него отвечает сам элемент. У него есть специальное свойство cacheable
. Пример можно посмотреть в методе pdoTools::runSnippet()
. Т.е. если вызывается кэшируемый сниппет через феном синтаксис, то парсер MODX переводит его в синтаксис MODX (с квадратными скобками и строкой параметров), выполняет его и сохраняет в кэше ресурса, а точнее в массиве, где ключом будет полный тег сниппета в формате MODX синтаксиса, а значением — результат выполнения этого сниппета. А в контенте ресурса этот Fenom тег так и останется (по аналогии с некэшируемыми тегами MODX). Таким образом, при следующем запросе, этот Fenom тег сниппета нужно опять обработать, но перед тем, как запустить указанный сниппет, парсер MODX опять переведёт его в синтаксис MODX и проверит кэш ресурса на наличие готового ответа.
Для того, чтобы вызвать некэшированный сниппет, нужно в его названии первым символом указать знак "!".
Как видите, принципы работы со стандартными кэшированными тегами и Fenom отличаются. И Fenom тут немного проигрывает. Кроме того, пользователи, имеющие опыт работы с фреймворками, могут заметить, что шаблонизатор фреймворка запускается один раз и сохраняет один скомпилированный файл шаблона, тогда как в данном случае Fenom вынужден запускаться и компилировать файлы столько раз, сколько чанков ему подсунут, плюс ещё контент ресурса.
Вот такая большая статья получилась. Как видите, тут есть над чем подумать.
П.С. В процессе разбора кода парсера MODX попадались места, где приходилось морщиться от недоумения и запаха. Встречались как безобидные вещи типа
if (is_string($key) && array_key_exists($key, $this->placeholders)) { $placeholder= & $this->placeholders["{$key}"]; } ... $escOpen = $escOpen == true ? false : true;
так и замороченные как в обработчике запросов. Кроме того, я так и не смог найти ответ на вопрос — нафига нужно событие «OnParseDocument», которое запускается на каждую итерацию и ничего не даёт. Всё это вызывает неподдельную грусть.
Вы должны авторизоваться, чтобы оставлять комментарии.
Комментарии ()