MODX. Шаблонизатор Fenom. Что это и как им пользоваться

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

Достоинства

Работает быстрее чем родной парсер 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 — кэширование скопмилированных шаблонов. Имеет смысл только для сложных чанков на рабочих сайтах, по умолчанию отключено.
  • pdotools_fenom_save_on_errors — сохраняет ошибки компиляции Fenom в специальный файл, для последующей отладки.

Синтаксис

Комментарии для шаблонизатора Fenom:

  • {ignore}{/ignore}
  • {**}

Символ конкатенации (присоединение строк):

  • ~

Экранирование:

  • \

Теги MODX

Простой вызов

MODX Fenom
[[+id]] {$id}
[[+id:default=`test`]] {$id ?: 'test'}
[[+id:is=``:then=`test`:else=`[[+pagetitle]]`]] {$id == ''? 'test': $pagetitle}

{$_modx} — безопасный доступ к переменным и методам системы (обращение к классу microMODX)

[[*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()}  вывод записей из lexicon

Особенности плейсхолдеров

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

Обычные плейсхолдеры можно вывести так:

{$pagetitle}

{$_pls} — вывод плейсхолдеров с точкой или дефисом в названии

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

<!-- Вывод доп поля со стандартным префиксом tv. -->
{$_pls['tv.img']}

<!-- Вывод через переменную -->
{var $tv_name = $_pls['tv-name']}
{$tv_name}

Если плейсхолдер был выставлен в глобальный массив modX::placeholders, то его можно получить так:

{$_modx->getPlaceholder('placeholder')}

{$_modx->getPlaceholder('placeholder')} помогает когда плейсхолдер устанавливается в сниппете при помощи $modx->setPlaceholder($value,'placeholder'); но в шаблоне и чанках не видны результаты при вызове вида {$placeholder}. 

Если сниппет не умеет сохранять результаты своей работы в плейсхолдер, можно присвоить их переменной Fenom:

{set $date = 'dateAgo' | snippet : ['input' => '2016-09-10 12:55:35']}

...

Дата: {$date}.

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

{$_modx->resource['tv-name']}

Переменные

Если надо установить переменную, не выводя ее результат, используются тег {set} (в старом синтаксисе {var})

{set $var=(expr)}

{set $var}
  ... any content ...
{/set}

{set $var|modifiers}
  ... any content ...
{/set}

{set $v = 5}
{set $v = "value"}
{set $v = $x+$y}
{set $v = 4}
{set $v = $z++ + 1}
{set $v = --$z}
{set $v = $y/$x}
{set $v = $y-$x}
{set $v = $y*$x-2}
{set $v = ($y^$x)+7}

{set $v = [1,2,3]}
{set $v = []}
{set $v = ["one"|upper => 1, 4 => $x, "three" => 3]}
{set $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]}

{set $v = count([1,2,3])+7}

// В качестве значения переменной можно задать результат отрисовки фрагмента шаблона
{set $v}
    Some long {$text|trim}
{/set}

// применение модификатора к данным перед тем как они будут сохранены в переменную
{set $v|escape} {* применение можификатора к значению *}
    Some long {$text|trim}
{/set}

Тег {add} делает тоже самое что и тег {set} за исключением того что сначала проверяет наличие переменной и если переменной нет — задет новое значение.

{add $var = 'value'}

// схема работы тега
{if $var is not set}
    {set $var = 'value'}
{/if}

Работа с массивами в fenom

Заполнение массива:

{set $array = [
    "key" => "value",
    "key2" => "value2",
]}

{set $array.key = value}

{set $array[] = value}

Для проверки присутствия значения в массиве или строке используется оператор in, или его инвертированная версия not in.

  • $a in list $b — значение $a содержится в массиве значений $b;
  • $a in keys $b — массив $b имеет ключ $a;
  • $a in string $b — значение $a содержится в $b как подстрока;
  • $a in $b — значение $a содержится в $b, где $b может быть строкой, обычным или ассоциативным массивом. Этот вариант долгий так как требуется проверить типы переменной $b. Однако если вместо $b явно задан массив или строка то оператор сам адаптируется для быстрого поиска.

Проверка присутствия в массиве:

{'df' in 'abcdefg'}
{5 in [1, 5, 25, 125]}
{99 in keys [1, 5, 25, 99 => 125]}

Массивы Get и Post

<!-- шаблон -->
{$.method.var_name}

<!-- выведет данные с именем tag из массива get -->
{$.get.tag}

<!-- выведет данные с именем page из массива post -->
{$.post.page}

Условия if, else, switch, foreach

Список возможных операторов fenom

Условие «если, то»:

{if $idx == 1}
    idx = 1
{elseif $idx == 4 || ($idx > 3 && $idx < 5)}
    idx = 4
{else}
    idx = ?
{/if}

Короткая запись if (тернарный оператор)

// выбор между полями
{$_modx->resource.longtitle ? $_modx->resource.longtitle : $_modx->resource.pagetitle}

// значение по умолчанию
{$_modx->resource.introtext ?: 'аннотация не заполнена'}

Дополнительная информация про условие if, else

Проверка, если дополнительное поле заполнено:

{if $_modx->resource.tags != ''}
    Вывод информации
{/if}

Переключение: 

{switch $idx}
   {case 1} idx=1
   {case 4} idx=4
   {default} idx = $idx
{/switch }

Цикл foreach:

{var $array = [
               0 => 'val1', 
               1 => 'val2'
              ]}

{foreach $array as $key => $value}
    key = {$key}, value = {$value}
{/foreach}

Дополнительная информация про цикл foreach

Foreach работает только с массивами, объектами и интервалами. Имеет несколько дополнительных параметров для гибкой настройки. К примеру, можно добавить теги:

  • {break} — выходит из цикла до достижения последней итерации. 
  • {continue} — прерывает текущую итерацию. Если текущая итерация была последней, цикл завершается.
  • {foreachelse} — ограничивает код, который должен быть выполнен, если итерируемый объект пуст.
{var $list = []}
{foreach $list as $value}
 <div>Значение: {$value}</div>
{foreachelse}
 <div>Массив пуст</div>
{/foreach}

Помимо этого присутствует возможность указать номер итерации в цикле:

  • index — указание номера итерации;
  • first — первая итерация;
  • last — последняя итерация.

 Вычисление итерации путем создания соответствующей переменной index=$index:

<!-- Определение первой итерации -->
{foreach $list as $value first=$first}
 <div>{if $first} Первое значение {/if} {$value}</div>
{/foreach}

<!-- Определение последней итерации -->
{foreach $list as $value last=$last}
 <div>{if $last} Первое значение {/if} {$value}</div>
{/foreach}

<!-- Определение номера итерации -->
{foreach $list as $value index=$index}
 <div>№{$index}: {$value}</div>
{/foreach}


Вычисление итерации путем указания постфикса для переменной {$value@index}:

<!-- Настройка первой итерации в цикле --> 
{foreach $list as $value}
 <div>{if $value@first} Первое значение {/if} {$value}</div>
{/foreach}

<!-- Настройка последней итерации в цикле --> 
{foreach $list as $value}
 <div>{if $value@first} Первое значение {/if} {$value}</div>
{/foreach}

<!-- Вычисление номера итерации в цикле --> 
{foreach $list as $value}
 <div>№{$value@index}: {$value}</div>
{/foreach}

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

// упрощенная конструкция
{'pdoSitemap' | snippet}

// упрощенная конструкция с параметрами и отсутствием кеширования
{'!pdoPage' | snippet : [
    'parents' => 0,
    'showLog' => 1,
    'element' => 'psoResources',
    'where' => ['isfolder' => 1],
    'showLog' => 1,
]}

// полная конструкция
{$_modx->runSnippet('pdoSitemap')}

// полная конструкция с параметрами, расширением и отсутствием кеширования
{$_modx->runSnippet('!pdoPage@PropertySet', [
    'parents' => '2,'~$_modx->resource.id,  // конкатенация внутри fenom
    'showLog' => 1,
    'element' => 'psoResources',
    'where' => ['isfolder' => 1],
    'showLog' => 1,
])}
{$_modx->getPlaceholder('page.total')}
{$_modx->getPlaceholder('page.nav')}

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

Для вывода чанков запускается pdoTools, можно использовать все его возможности:

{$_modx->getChunk('MyChunk@PropertySet')}

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

{$_modx->getChunk('@TEMPLATE Base Template')}

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

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

{$_modx->getChunk('
    @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' => '@INLINE {$id} - {$pagetitle}',
        'showLog' => 1,
    ])}
    // set $null — чтобы cacheManager->set не вывел 1 (т.е. true) на страницу
    {set $null = $_modx->cacheManager->set('cache_key', $snippet, 1800)} // 30 минут
{/if}

{$snippet}

Посмотреть этот кэш можно в /core/cache/default/

Системные процессоры

Можно запускать системные процессоры (если хватит прав ):

{$_modx->runProcessor('resource/update', [
    'id' => 10,
    'alias' => 'test',
    'context_key' => 'web',
])}

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

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

Вывод информации только пользователям, которые авторизованы в менеджере:

{if $_modx->hasSessionContext('mgr')}
    {$_modx->getInfo()}
{/if}

Обработка по id (fastField)

{id | параметр : [доп указания]}

{3 | url : ['scheme' => 'full'] : ['param' => 'value']}   // устанавливает ссылку на ресурс с заданными параметрами
{3 | ismember : ['Administrator', 'Manager']}             // проверяет, состоит ли пользователь с указанным id в группах
{3 | user : 'fullname'}                                   // выводит указанное поле пользователя
{3 | resource : 'my_tv'}                                  // выводит указанное поле ресурса

Обработка данных

// разбивает строку на массив
{$string|split:","}

{var $fruits1 = "banana,apple,pear"|split:','}
$fruits1 is array ["banana", "apple", "pear"]

{var $fruits2 = "banana,apple,pear"|split:',apple,'}
$fruits2 is array ["banana", "pear"]

Работа с ресурсами

Получить и вывести один ресурс без использования сниппета:

{var $resource = $_modx->getResource(1)}

{$_modx->getChunk('@INLINE <p>{$id} {$pagetitle}</p>', $resource)}

Получить и вывести ресурсы без использования сниппета:

{var $resources = $_modx->getResources(
    ['published' => 1, 'deleted' => 0],
    ['sortby' => 'id', 'sortdir' => 'ASC', 'limit' => 50]
)}
{foreach $resources as $resource}
    {$_modx->getChunk('@INLINE <p>{$id} {$pagetitle}</p>', $resource)}
{/foreach}

Получить 1 уровень потомков ресурса с id 3:

// параметры по умолчанию
// $id = null, $depth = 10, array $options = array()

{$_modx->getChildIds(3, 1)} 

Получить родителей ресурса с id 33:

// параметры по умолчанию
// $id = null, $height = 10, array $options = array()

{$_modx->getParentIds(33,5)} 

Применение модификаторов

// { объект | модификатор : 'значение' | модификатор : 'значение'}

{$_modx->resource.pagetitle | limit : '3' | lowercase}

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

{$_modx->regClientCss('/assets/css/style.css')}             // регистрация css
{$_modx->regClientScript('/assets/css/script.js')}          // регистрация js

{$_modx->sendForward(10)}                                   // загрузить ресурс без изменения url
{$_modx->sendRedirect('http://yandex.ru')}                  // перенаправление, редирект

{$_modx->setPlaceholder('key', 'value')}                    // установить плейсхолдер
{$_modx->getPlaceholder('key')}                             // получить плейсхолдер

{if $res = $_modx->findResource('url-to/doc/')}             // найти ресурс
    {$_modx->sendRedirect( $_modx->makeUrl($res) )}         // создать url
{/if}

Усложненный синтаксис

В некоторых случаях появляется потребность вставить тег fenom в тег fenom. Такая возможность существует:

{"Название страницы: {$pagetitle}"}      

Допускаются не только переменные, но и различные имеющиеся возможности шаблонизатора феном. Но, если необходимо вывести именно код, без его обработки, то можно добавить экранирование. Поскольку экранировать фигурную скобку нельзя, экранируется знак $: 

{"Название страницы: {\$pagetitle}"}
<!-- выведет "Название страницы: {$pagetitle}" -->

Служебная информация

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

{$_modx->getInfo(string, bool, string)}
  • string — вывести определённый ключ из массива данных (пусто, по умолчанию);
  • bool — вывести все данные как строку, а не массив (true, по умолчанию);
  • string — оформить строки в чанк (Например: @INLINE {$key}: ${value}).

Вывод по умолчанию (строка со всеми данными):

{$_modx->getInfo()}
// queries: 39
// totalTime: 0.1067 s
// queryTime: 0.0069 s
// phpTime: 0.0998 s
// source: database

Вывод конкретного значения:

{$_modx->getInfo('totalTime')}
// 0.1067 s
{$_modx->getInfo('source')}
// database

Оформление строк в свой чанк:

{$_modx->getInfo('', true, '@INLINE {$key} => {$value}')}
// queries => 39
// totalTime => 0.1155 s
// queryTime => 0.0091 s
// phpTime => 0.1064 s
// source => database

Можете добавить строки в лексикон и выводить ключи через него:

{$_modx->lexicon->load('pdotools:default')}
{$_modx->getInfo('', true, '@INLINE {$_modx->lexicon("pdotools_" ~ $key)} => {$value}')}

Или оформить без лексиконов, произвольно. Просто присваиваем переменной массив данных и выводим его ключи:

{set $info = $_modx->getInfo('', false)}
Время работы: {$info.totalTime}
Время запросов: {$info.totalTime}
Количество запросов: {$info.queries}
Источник: {$info.source}

Вывод информации только пользователям, которые авторизованы в менеджере:

{if $_modx->hasSessionContext('mgr')}
    {$_modx->getInfo()}
{/if}

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

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

{include} — используются для включения других шаблонов в текущий.
Например, можно просто подгрузить содержимое чанка:

Обычный чанк 
{include 'имя чанка'}

Шаблон modTemplate 
{include 'template:имя шаблона'}

Чанк с набором параметров
{include 'chunk@propertySet'}
{include 'template:Name@propertySet'}

Передача переменных в блок, например чанк:

{include 'divChankName' class=“col-md-12“}

<!-- Вызывается в чанке с помощью переменной $class -->
<div $class>
  У этого блока установлен класс col-md-12
</div>

{include} в официальной документации

{extend} —  возможность подключения шаблона или чанка для последующего его расширения.

требует включенной системной настройки pdotools_fenom_parser.

Пример: базовый шаблон «Fenom Base» включает обычные чанки и определяет несколько блоков {block}, которые можно расширить в другом шаблоне:

<!DOCTYPE html>
<html lang="en">
<head>
    {include 'head'}
</head>
<body>
    {block 'navbar'}
        {include 'navbar'}
    {/block}
    <div class="container">
            <div class="content">
                {block 'content'}
                    {$_modx->resource.content}
                {/block}
            </div>
            <div class="sidebar">
                {block 'sidebar'}
                    Sidebar
                {/block}
            </div>
        {block 'footer'}
                {include 'footer'}
        {/block}
    </div>
</body>
</html>

Дочерний шаблон «Fenom Extended», в котором подключается «Fenom Base» и расширяется блок content:

{extends 'template:Fenom Base'}
{block 'content'}
    <h3>{$_modx->resource.pagetitle}</h3>
    <div class="jumbotron">
        {parent}
    </div>
{/block}

Также можно расширять чанки, но, для чанков не нужен префикс template.

{extends} в официальной документации

?

Благодарю за ответ!

Была ли эта информация полезной?

Здравствуйте!

Позвольте представиться, меня зовут Марина. Более 10 лет я занимаюсь обслуживанием сайтов и развитием интернет проектов. Если вы хотите избавиться от хлопот связанных с созданием и поддержкой сайта, тогда вы попали по адресу. При работе с сайтами я предоставляю качественные услуги, ориентируясь на ваши индивидуальные потребности. Для связи со мной воспользуйтесь формой обратной связи.

Инсталента
Пожалуйста, активируйте JavaScript.
Please enable JavaScript.
return; 1