• Блог
  • Знакомьтесь, шаблонизатор MODX

В сообществе периодически возникают вопросы о шаблонизаторах, проблемах с ними и т.п. Ответить на них поможет понимание того, как работает механизм шаблонизации в MODX. Давайте попробуем вместе по винтикам разобрать принцип парсинга контента ресурса. Конечно, тут больше подошёл бы видео формат. Но я сейчас на самоизоляции далеко от своего рабочего компьютера, поэтому мои возможности ограничены. И так, приступим.

Шаблонизация в MODX

Структура формирования HTML ответа в MODX схожа с другими шаблонизаторами — HTML шаблон + контент. Но примерение сильно отличается. Виной тому совершенно другой подход к парсингу содержимого. В MODX нет строгих правил что нужно писать в шаблоне, а что в контенте ресурса. Часто встречаются ситуации, когда контент содержит не только текст, но и различные элементы (сниппеты, чанки). А бывает и элементы логики вставляют. Достаточно трудно представить себе такое, скажем, в Laravel. Даже скажем по другому — такое невозможно!

В MODX решили не использовать готовые шаблонизаторы, а придумали свой.

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

И в этом есть своя логика. Разработчики MODX придумали более простой синтаксис, который облегчает жизнь непрограммистам. Они ввели несколько типов тегов, понятных и простых в использовании. Вместо кодирования на php — сниппеты с параметрами. Для удобного структурирования и многоразового использования — чанки. А ещё плейсхолдеры лексиконов и системных настроек. И всё это увязано с возможностью управления кэшированием (через !). А фильтры и наборы параметров добавляют дополнительные возможности.

Если представить, что MODX всё-таки CMS(!) и сфера его применения — небольшие и средние сайты, то данный шаблонизатор, не смотря на свои ограниченные возможности, легко справится со всем поставленными задачами. Наверняка найдутся люди, которые не согласятся с таким определением MODX. Хочу им сказать, что есть много факторов, почему я так позиционирую MODX — начиная от архитектурных ограничений, элементов, хранящихся в БД и заканчивая отсутствием развития. Но вернёмся к теме.

Но кроме всех этих вещей они ещё и придумали свою логику парсинга контента — многократный и рекурсивный. Что совершенно меняет подход к вёрстке и размывает границу между HTML шаблоном и содержанием ресурса. И как следствие — шаблон страницы не компилируется в готовый php скрипт, который затем выполняется один раз и очень быстро, а каждый раз ищет в тексте теги MODX и рекурсивно парсит их. Первый раз все, а затем только некэшированные. Как вы понимаете, это сказывается на времени обработки. А в случае с подключённым Fenom не сильно спасает и кэшированный вызов. При данном подходе крайне сложно создать полноценный шаблонизатор. Но для обозначенных целей его функциональности вполне хватает.

Парсер MODX

Давайте теперь перейдём непосредственно к механике парсинга документа. Условие следующее — у нас есть документ с шаблоном, у которого нет кэша. Документ хранит какую-то инфморацию. Например, статью.

Подготовка HTML ответа происходит в три этапа:

  1. парсинг кэшируемых тегов;
  2. парсинг некэшируемых тегов без удаления нераспарсенных;
  3. парсинг некэшируемых тегов с удалением всех нераспарсенных;

Парсинг кэшируемых тегов

Первым делом шаблонизатор формирует структуру ответа. Он подгружает из базы данных шаблон, берёт его содержание и парсит только кэшируемые теги в нём. И самый важный тег, который связывает шаблон с документом — [[*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», которое запускается на каждую итерацию и ничего не даёт. Всё это вызывает неподдельную грусть.

0   3261

Комментарии ()

    Вы должны авторизоваться, чтобы оставлять комментарии.

    Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.