Pull to refresh

MODX Revolution встречает Fenom

Reading time 14 min
Views 45K
В последнее время в англоязычном сообществе MODX много рассуждений на тему «как нам жить дальше». Все на перебой обсуждают грядущую (через несколько лет, полагаю) мажорную версию 3, а мы пока улучшаем своими дополнениями текущую.

Свежее событие, которым я бы хотел поделиться с широкой аудиторией — это выпуск новой версии pdoTools с шаблонизатором Fenom, которая позволит вам полностью избавиться от нагромождения тегов в условиях чанков и переписать их на простом и понятном языке шаблонизатора.

Процедура не требует изменений в работе сайта, просто обновите pdoTools до версии 2.0 и можно использовать новый синтаксис. Самое приятное, что теги MODX отлично соседствуют с Fenom и работают вместе без каких-либо проблем. Простой пример для затравки:
{if $parent == 3}
    [[!pdoMenu?parents=`0`]]
{else}
    [[!pdoResources?parents=`1,2,3`]]
{/if}
Под катом огромное количество информации о парсере pdoTools, которую я еще ни разу не собирал в одном месте.

Итак, парсер pdoTools представляет из себя отдельный класс, который прописывается в системных настройках MODX и перехватывает обработку тегов на странице.
В старых версиях компонента, включение парсера нужно было подтверждать при установке, но с версии 2.1.1-pl он включается по умолчанию. Если, по каким-то причинам, вас это не устраивает — удалите системные настройки
  • parser_class — имя класса парсера
  • parser_class_path — путь к классу парсера

По умолчанию в MODX нет этих настроек, они нужны только для подключения стороннего парсера, как в нашем случае.

Принцип работы


pdoParser может быть использован в двух случаях:
  • при рендере чанка сниппетом — это происходит всегда и во всех сниппетах, использующих pdoTools, независимо от системных настроек.
  • при рендере страницы — только если парсер включен в настройках системы.


Обработка чанка


В классе pdoTools для этого есть 2 метода, очень похожих на таковые в классе modX:

  • getChunk — полная обработка чанка, может задействовать родной парсер MODX
  • parseChunk — только замена плейсхолдеров на значения, modParser не вызывается

Основной особенностью работы этих методов является то, что для загрузки чанка используется protected метод _loadChunk, который может не только загружать чанк из базы данных, но и превращает в него произвольные строки.

Варианты чанков


Итак, оба метода pdoTools поддерживают следующие виды имён чанков:

@INLINE или @CODE


Один из самых популярных вариантов — указание тела чанка прямо на странице. Например:
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;INLINE <p>{{+id}} - {{+pagetitle}}</p>`
]]

В таком указании есть особенность, о которой многие люди не задумываются — все плейсхолдеры внутри чанка будут обработаны парсером до вызова сниппета.

То есть, если вызвать сниппет на странице вот так:
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;INLINE <p>[[+id]] - [[+pagetitle]]</p>`
]]

и в памяти системы при этом выставлены плейсхолдеры [[+id]] или [[+pagetitle]], то в сниппет придёт уже обработанный чанк и вы получите на странице одинаковые строки, типа:
15 - тест
15 - тест
15 - тест

Просто одинаковые значения, которые выставил какой-то другой сниппет раньше. Именно поэтому в примере у нас такие необычные плейсхолдеры — {{+}} вместо [[+]]. Системный парсер их не трогает, а pdoTools заменяет их на нормальные во время работы.

Вы можете использовать фигурные скобочки в качестве обрамления плейсхолдеров во всех чанках pdoTools — он сам превратит их в [[+]] при загрузке.

По этой же причине у вас никогда не будут работать вызовы сниппетов и фильтров в INLINE чанках. Вот так работать не будет:
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;INLINE <p>[[+id]] - [[+pagetitle:default=`название страницы`]]</p>`
]]

А вот так — без проблем
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;INLINE <p>{{+id}} - {{+pagetitle:default=`название страницы`}}</p>`
]]

Помните об этом нюансе при использовании INLINE чанков.

&commat;FILE


Многие люди обвиняют MODX в том, что он не умеет хранить чанки в файлах и вынуждает лишний раз работать с базой данных. Это и неудобно для системы контроля версий, и медленнее.

С версии 2.2 MODX предлагает использовать для этих целей статичные элементы, но по ряду причин, этот способ всё равно может быть менее удобен, чем прямая работа с файлами.

pdoTools открывает такую возможность при указании &commat;FILE:
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;FILE resources/mychank.tpl`
]]

В целях безопасности, использовать можно только файлы с расширеним html и tpl, и только из определённой, заранее заданной директории. По умолчанию это: /assets/elements/chunks/.

Вы можете указать свою собственную директорию для файлов через параметр &tplPath:
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;FILE resources/mychunk.tpl`
    &tplPath=`/core/elements`
]]

Файл будет загружен из файла /core/elements/resources/mychunk.tpl от корня сайта.

&commat;TEMPLATE


Этот тип чанка позволяет использовать шаблоны системы (т.е. объекты modTemplate) для оформления вывода.
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;TEMPLATE Base Template`
]]

Если указан пустой шаблон и в выбранных записях есть поле template с id или именем шаблона, то запись будет обёрнута в этот шаблон:
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;TEMPLATE`
]]

Это такой аналог сниппета renderResources.

При выводе шаблона можно указывать и набор параметров (как у сниппетов):
[[!pdoResources?
    &parents=`0`
    &tpl=`&commat;TEMPLATE Base Template&commat;MyPropertySet`
]]

Тогда значения из этого набора будут вставлены в шаблон.

Обычные чанки


Это режим по умолчанию, который загружает чанк из базы данных:
[[!pdoResources?
    &parents=`0`
    &tpl=`MyChunk`
]]

Точно так же поддерживаются и наборы параметров:
[[!pdoResources?
    &parents=`0`
    &tpl=`MyChunk&commat;MyPropertySet`
]]

Эти способы загрузки чанков работают во всех родных сниппетах pdoTools и во всех других, которые используют методы pdoTools getChunk и parseChunk.

Метод getChunk


Объявление этого метода выглядит так:
getChunk(string $chunkName, array $properties, bool $fastMode = false)

Метод загружает указанный чанк (следуя указанию &commat;BINDING, если есть) и полностью обратывает его, заменяя все плейсхолдеры на переданные значения (параметр $properties).

Третий параметр fastMode вырезает все оставшиеся необработанные плейсхолдеры, чтобы не было лишних тегов на странице. Если этого не сделать, то парсер будет пытаться рекурсивно разобрать эти теги (до 10 итераций по умолчанию), что может привести к замедлению работы.

Рекурсивный парсер — это одно из достоинств MODX и специально оставленные теги очень часто встречаются в логике работы сниппетов системы. Поэтому fastMode отключен по умолчанию и использовать его нужно, только если вы уверены в том, что делаете.

Парсер pdoTools не будет вызывать системный парсер, если смог самостоятельно разобрать все плейсхолдеры. Если же в чанке остались какие-то вызовы фильтров или сниппетов, то работа передаётся в modParser, что требует дополнительное время на обработку.

Метод parseChunk


А этот метод объявлен вот так:
parseChunk(string $name, array $properties, string $prefix = '[[+', string $suffix = ']]')

Он также создаёт чанк из указанного имени, разбирая &commat;BINDING, если есть, а потом просто заменяет плейсхолдеры на значения, без особых обработок.

Это самый простой и быстрый способ оформления данных в чанки.

Обработка страницы


Если pdoParser включен в настройках, то он вызывается и для обработки всей страницы при выводе её пользователю.

При использовании этого парсера все чанки и дополнения MODX обрабатываются немного быстрее. Всего лишь "немного" потому, что он не берёт на себя условия и фильтры, обрабатывая только простенькие теги, типа [[+id]] и [[~15]]. Однако, он это делает быстрее modParser, потому что не создаёт лишних объектов.

Помимо возможной прибавки скорости, вы получаете еще и новые возможности по удобному выводу данных из разных ресурсов.

Теги fastField


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

Он добавляет в систему обработку дополнительных плейсхолдеров, типа [[#15.pagetitle]]. С разрешения автора, этот функционал уже включен в pdoParser, и даже немного расширен.

Все теги fastField начинаются с # и дальше содержат или id нужного ресурса, или название глобального массива.

Вывод обычных полей ресурсов:
[[#15.pagetitle]]
[[#20.content]]

ТВ параметры ресурсов:
[[#15.date]]
[[#20.some_tv]]

Поля товаров miniShop2:
[[#21.price]]
[[#22.article]]

Массивы ресурсов и товаров:
[[#12.properties.somefield]]
[[#15.size.1]]

Суперглобальные массивы:
[[#POST.key]]
[[#SESSION.another_key]]
[[#GET.key3]]
[[#REQUEST.key]]
[[#SERVER.key]]
[[#FILES.key]]
[[#COOKIE.some_key]]

Можно указывать любые поля в массивах:
[[#15.properties.key1.key2]]

Если вы не знаете, какие значения находятся внутри массива — просто укажите его и он будет распечатан полностью:
[[#GET]]
[[#15.colors]]
[[#12.properties]]

Теги fastField можно сочетать с тегами MODX:
[[#[[++site_start]].pagetitle]]

[[#[[++site_start]]]]

Шаблонизатор Fenom


Поддержка шаблонизатора Fenom появилась в pdoTools с версии 2.0, после чего он стал требовать PHP 5.3+.

Он работает гораздо быстрее, чем родной modParser, и если вы перепишите свой чанк так, что в нём не будет ни одного тега MODX, то modParser и вовсе не будет запускаться. При этом, конечно, одновременная работа и старых тегов, и новых в одном чанке допускается.

На обработку шаблонизатором влияют следующие системные настройки:
  • pdotools_fenom_default — включает обработку через Fenom чанков pdoTools. Включено по умолчанию.
  • pdotools_fenom_parser — включает обработку шаблонизатором всех страниц сайта. То есть, не только чанков, но и шаблонов.
  • pdotools_fenom_php — включает поддержку PHP функций в шаблонизаторе. Очень опасная функция, так как любой менеджер получит доступ к PHP прямо из чанка.
  • pdotools_fenom_modx — добавляет системные переменные {$modx} и {$pdoTools} в шаблоны Fenom. Тоже очень опасно — любой менеджер может управлять объектами MODX из чанков.
  • pdotools_fenom_options — JSON строка с массивом настроек согласно официальной документации. Например: {"auto_escape":true,"force_include":true}
  • pdotools_fenom_cache — кэширование скопмилированных шаблонов. Имеет смысл только для сложных чанков на рабочих сайтах, по умолчанию отключено.

Итак, по умолчанию Fenom включен для работы только в чанках, которые проходят через pdoTools. Это вполне безопасно и менеджеры системы не получают никаких дополнительных возможностей, кроме более удобного синтаксиса и высокой скорости работы.

Включение pdotools_fenom_parser позволяет использовать синтаксис Fenom прямо в контенте документов и шаблонах страниц, но есть один нюанс — шаблонизатор может неверно реагировать на фигурные скобочки, которые в MODX очень любят.

В таких случаях автор рекомендует использовать тег {ignore}.

Если вы планируете включить Fenom глобально для всего сайта, вам нужно проверить, на всех ли страницах он нормально работает.

Синтаксис


Для начала советую прочитать официальную документацию, а дальше мы рассмотрим синтаксис применительно к MODX.

Все переменные от сниппетов передаются в чанк как есть, поэтому переписывать старые чанки на новый синтаксис — сплошное удовольствие.
MODX Fenom
[[+id]] {$id}
[[+id:default=`test`]] {$id ?: 'test'}
[[+id:is=``:then=`test`:else=`[[+pagetitle]]`]] {$id == ''? 'test': $pagetitle}

Для использования более сложных сущностей, в pdoParser предусмотрена служебная переменная {$_modx}, которая даёт безопасный доступ к некоторым переменным и методам системы.
MODX Fenom
[[*id]] {$_modx->resource.id}
[[*tv_param]] {$_modx->resource.tv_param}
[[%lexicon]] {$_modx->lexicon('lexicon')}
[[~15]] {$_modx->makeUrl(15)}
[[~[[*id]]]] {$_modx->makeUrl($_modx->resource.id)}
[[++system_setting]] {$_modx->config.system_setting}

Помимо этого вам доступны переменные:
{$_modx->config} — системные настройки
{$_modx->config.site_name}
{$_modx->config.emailsender}
{$_modx->config['site_url']}
{$_modx->config['any_system_setting']}

{$_modx->user} — массив текущего пользователя. Если он авторизован, то добавляются и данные из профиля:
{if $_modx->user.id > 0}
    Привет, {$_modx->user.fullname}!
{else}
    Вам нужно авторизоваться.
{/if}

{$_modx->context} — массив с текущим контекстом
Вы находитесь в контексте {$_modx->context.key}

{$_modx->resource} — массив с текущим ресурсом, это вы уже видели в примерах выше
{$_modx->resource.id}
{$_modx->resource.pagetitle}
{$_modx->makeUrl($_modx->resource.id)}

{$_modx->lexicon} — объект (не массив!) modLexicon, который можно использовать для загрузки произвольных словарей:
{$_modx->lexicon->load('ms2gallery:default')}
Проверка словарей ms2Gallery: {$_modx->lexicon('ms2gallery_err_gallery_exists')}

За вывод записей отвечает отдельная функция {$_modx->lexicon()}.

Плейсхолдеры с точкой


Fenom использует точку для доступа к значению массива, а MODX обычно выствляет так плейсхолдеры из массивов. Соотвественно, для тегов [[+tag.sub_tag]] аналогов в Fenom не предусмотрено.

Поэтому для подобных плейсхолдеров вам необходимо использовать вторую служебную переменную — {$_pls}:
{$_pls['tag.subtag']}

Вывод сниппетов и чанков


Переменная {$_modx} на самом деле представляет собой простой и безопасный класс microMODX

Поэтому сниппеты и чанки вызываются так:
{$_modx->runSnippet('!pdoPage&commat;PropertySet', [
    'parents' => 0,
    'showLog' => 1,
    'element' => 'psoResources',
    'where' => ['isfolder' => 1],
    'showLog' => 1,
])}
{$_modx->getPlaceholder('page.total')}
{$_modx->getPlaceholder('page.nav')}

Как видите, синтаксис практически полностью повторяет PHP, что открывает новые возможности. Например, можно указывать массивы, вместо JSON строк.

По умолчанию все сниппеты вызываются кэшированными, но вы можете добавить ! перед именем — как в тегах MODX.

Если для вызова сниппета используется родной метод MODX, то для вывода чанков запускается pdoTools, и вы можете использовать все его возможности:
{$_modx->getChunk('MyChunk&commat;PropertySet')}

{$_modx->parseChunk('MyChunk', [
    'pl1' => 'placeholder1',
    'pl2' => 'placeholder2',
])}

{$_modx->getChunk('&commat;TEMPLATE Base Template')}

{$_modx->getChunk('&commat;INLINE
    Имя сайта: {$_modx->config.site_name}
')}

{$_modx->getChunk(
    '&commat;INLINE Передача перемнной в чанк: {$var}',
    ['var' => 'Тест']
)}

{$_modx->getChunk('
    &commat;INLINE Передача переменной в вызов сниппета:
    {$_modx->runSnippet("pdoResources", [
        "parents" => $parents
    ])}
    Всего результатов: {$_modx->getPlaceholder("total")}
    ',
    ['parents' => 0]
)}

Примеры выше немного безумные, но вполне себе работают.

Управление кэшированием


В объекте {$_modx} доступен сервиc modX::cacheManager, который позволяет вам устанавливать произвольное время кэширование вызываемых сниппетов:
{if !$snippet = $_modx->cacheManager->get('cache_key')}
    {set $snippet = $_modx->runSnippet('!pdoResources', [
        'parents' => 0,
        'tpl' => '&commat;INLINE {$id} - {$pagetitle}',
        'showLog' => 1,
    ])}
    {set $null = $_modx->cacheManager->set('cache_key', $snippet, 1800)}
{/if}

{$snippet}

Посмотреть этот кэш можно в /core/cache/default/, в примере он сохраняется на 30 минут.

set $null = ... нужен, чтобы cacheManager->set не вывел 1 (т.е. true) на страницу.

А еще вы можете запускать системные процессоры (если прав хватит):
{$_modx->runProcessor('resource/update', [
    'id' => 10,
    'alias' => 'test',
    'context_key' => 'web',
])}

Проверка авторизации


Так как объекта с пользователем в {$_modx} нет, методы проверки авторизации и прав доступа вынесены непосредственно в класс:
{$_modx->isAuthenticated()}
{$_modx->hasSessionContext('web')}
{$_modx->hasPermission('load')}

Остальные методы


Эти методы должны быть знакомы всем разработчикам MODX, поэтому просто покажу их на примерах:
{$_modx->regClientCss('/assets/css/style.css')}
{$_modx->regClientScript('/assets/css/script.js')}

{$_modx->sendForward(10)}
{$_modx->sendRedirect('http://yandex.ru')}

{$_modx->setPlaceholder('key', 'value')}
{$_modx->getPlaceholder('key')}

{if $res = $_modx->findResource('url-to/doc/')}
    {$_modx->sendRedirect( $_modx->makeUrl($res) )}
{/if}

Расширение шаблонов


Использование шаблонизатора Fenom позволяет включать одни чанки (шаблоны в другие) и даже расширять их.

Например, вы можете просто подгрузить содержимое чанка:
Обычный чанк {include 'имя чанка'}
Шаблон modTemplate {include 'template:имя шаблона'}
Чанк с набором параметров
{include 'chunk&commat;propertySet'}
{include 'template:Name&commat;propertySet'}

Подробнее про {include} читайте в официальной документации.

Гораздо более интересная функция — {extends} шаблонов, она требует включенной системной настройки pdotools_fenom_parser.

Пишем базовый шаблон "Fenom Base":
<!DOCTYPE html>
<html lang="en">
<head>
    {include 'head'}
</head>
<body>
    {block 'navbar'}
        {include 'navbar'}
    {/block}
    <div class="container">
        <div class="row">
            <div class="col-md-10">
                {block 'content'}
                    {$_modx->resource.content}
                {/block}
            </div>
            <div class="col-md-2">
                {block 'sidebar'}
                    Sidebar
                {/block}
            </div>
        </div>
        {block 'footer'}
                {include 'footer'}
        {/block}
    </div>
</body>
</html>

Он включает обычные чанки (в которых, кстати, обычные плейсхолдеры MODX от компонента Theme.Bootstrap) и определяет несколько блоков {block}, которые можно расширить в другом шаблоне.

Теперь пишем "Fenom Extended":
{extends 'template:Fenom Base'}
{block 'content'}
    <h3>{$_modx->resource.pagetitle}</h3>
    <div class="jumbotron">
        {parent}
    </div>
{/block}

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

Точно также можно писать и расширять чанки, только обратите внимание, что для работы с modTemplate нужно указывать префикс template:, а для чанков нет — они работают по умолчанию во всех {include} и {extends}.

Тестирование производительности


Создаём новый сайт и добавляем в него 1000 ресурсов вот таким консольным скриптом:
<?php
define('MODX_API_MODE', true);
require 'index.php';

$modx->getService('error','error.modError');
$modx->setLogLevel(modX::LOG_LEVEL_FATAL);
$modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');

for ($i = 1; $i <= 1000; $i++) {
    $modx->runProcessor('resource/create', array(
        'parent' => 1,
        'pagetitle' => 'page_' . rand(),
        'template' => 1,
        'published' => 1,
    ));
}

Затем создаём 2 чанка: modx и fenom со следующим содержимым, соответственно:
<p>[[+id]] - [[+pagetitle]]</p>

и
<p>{$id} - {$pagetitle}</p>

И добавляем два консольных скрипта тестирования. Для родного парсера MODX
<?php
define('MODX_API_MODE', true);
require 'index.php';

$modx->getService('error','error.modError');
$modx->setLogLevel(modX::LOG_LEVEL_FATAL);
$modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');

$res = array();
$c = $modx->newQuery('modResource');
$c->select($modx->getSelectColumns('modResource'));
$c->limit(10);
if ($c->prepare() && $c->stmt->execute()) {
    while ($row = $c->stmt->fetch(PDO::FETCH_ASSOC)) {
        $res .= $modx->getChunk('modx', $row);
    }
}
echo number_format(microtime(true) - $modx->startTime, 4), 's<br>';
echo number_format(memory_get_usage() / 1048576, 4), 'mb<br>';
echo $res;

И для pdoTools:
<?php
define('MODX_API_MODE', true);
require 'index.php';

$modx->getService('error','error.modError');
$modx->setLogLevel(modX::LOG_LEVEL_FATAL);
$modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');
$pdoTools = $modx->getService('pdoTools');

$res = array();
$c = $modx->newQuery('modResource');
$c->select($modx->getSelectColumns('modResource'));
$c->limit(10);
if ($c->prepare() && $c->stmt->execute()) {
    while ($row = $c->stmt->fetch(PDO::FETCH_ASSOC)) {
        $res .= $pdoTools->getChunk('fenom', $row);
        //$res .= $pdoTools->getChunk('modx', $row);
    }
}
echo number_format(microtime(true) - $modx->startTime, 4), 's<br>';
echo number_format(memory_get_usage() / 1048576, 4), 'mb<br>';
echo $res;

Так как pdoTools понимает оба синтаксиса, для него 2 теста — в режиме тегов MODX, и в режиме Fenom.
В скриптах есть указание limit = 10, дальше в таблице я привожу цифры с его возрастанием:
Limit MODX pdoTools (MODX) pdoTools (Fenom)
10 0.0369s 8.1973mb 0.0136s 7.6760mb 0.0343s 8.6503mb
100 0.0805s 8.1996mb 0.0501s 7.6783mb 0.0489s 8.6525mb
500 0.2498s 8.2101mb 0.0852s 7.6888mb 0.0573s 8.6630mb
1000 0.4961s 8.2232mb 0.1583s 7.7019mb 0.0953s 8.6761mb

А теперь, давайте немного усложним чанки — добавим в них генерацию ссылки для ресурса и вывод menutitle:
<p><a href="[[~[[+id]]]]">[[+id]] - [[+menutitle:default=`[[+pagetitle]]`]]</a></p>

и
<p><a href="{$_modx->makeUrl($id)}">{$id} - {$menutitle ?: $pagetitle}</a></p>

Limit MODX pdoTools (MODX) pdoTools (Fenom)
10 0.0592s 8.2010mb 0.0165s 7.8505mb 0.0346s 8.6539mb
100 0.1936s 8.2058mb 0.0793s 7.8553mb 0.0483s 8.6588mb
500 0.3313s 8.2281mb 0.2465s 7.8776mb 0.0686s 8.6811mb
1000 0.6073s 8.2560mb 0.4733s 7.9055mb 0.1047s 8.7090mb

Как видите, обработка чанков через pdoTools во всех случаях быстрее.
При этом заметно, что у чанков Fenom есть некоторый минимум для старта, который обусловлен необходимостью компиляции шаблона.

Заключение


Давайте подытожим возможности парсера pdoTools:
  • Быстрая работа
  • Загрузка чанков из разных источников, включая файлы
  • Поддержка тегов fastField
  • Поддержка шаблонизатора Fenom
    • Наследование шаблонов
    • Расширение шаблонов
    • Безопасный доступ к продвинутым функциям MODX


На данный момент pdoTools скачан более 40 000 раз из официального репозитория и более 10 000 из репозитория modstore.pro, что позволяет надеяться на широкое распространение новых технологий шаблонизации в MODX.

Большое спасибо хабраюзеру aco за замечательный шаблонизатор!
Tags:
Hubs:
+4
Comments 16
Comments Comments 16

Articles