• Блог
  • Fenom и параметры элементов

В прошлой статье мы рассмотрели некоторые особенности реализации шаблонизатора Fenom в MODX и отметили проблемы с кэшированием. Напомню, что сам Fenom кэшированием не занимается. Ему отводится лишь роль дополнительного парсера — обработал контент и отдал его MODX. А MODX позаботится о кэшировании (насколько позволяют его возможности). И тут можно наткнуться на подводный камень.

Рассмотрим следующую ситуацию. Вы вызываете на странице для меню сниппет pdoMenu или для вывода списка ресурсов сниппет pdoResources. Если логика несложная, то и код достаточно простой.

{'pdoMenu' | snippet : [
    'parents' => 0,
    'resources' => '-10',
    'tpl' => '@INLINE <li>{$pagetitle}</li>'
]}

Как видим, сниппет вызван кэшируемым. А как я отмечал в предыдущей статье, Fenom умеет кэшировать только чанки и сниппеты (в отличие от других тегов и файловых элементов). Поэтому Fenom распарсит этот вызов, передаст управление pdoTools, который создаст объект сниппета, укажет, что это кэшируемый элемент и отдаст его на выполнение MODX.

Отличие логики кэширования MODX синтаксиса от Fenom

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

В случае с Fenom сработает другая логика кэширования. MODX не умеет заменять тег Fenom на результат. Поэтому в кэше страницы теги Fenom никогда не удаляются. Но MODX будет сохранять результаты в отдельный кэш элементов. Для этого в массиве ресурса в кэше есть специальный ключ elementCache. Именно в него сохраняются кэшируемые чанки и сниппеты, вызванные через Fenom. Причем формат следующий — в качестве ключа указан полный код вызова сниппета (включая неуказанные при вызове параметры), а в качестве значения — результат.

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

Например, выше приведённый вызов в кэше будет выглядеть так:

// core/cache/resource/web/resources/1.cache.php
'elementCache' => 
  array(
    '[[pdoMenu?where=``&showLog=``&fastMode=``&level=`0`&parents=`0`&displayStart=``&resources=`-10`&templates=``&context=``&cache=``&cacheTime=`3600`&cacheAnonymous=``&plPrefix=`wf.`&showHidden=``&showUnpublished=``&showDeleted=``&previewUnpublished=``&hideSubMenus=``&useWeblinkUrl=`1`&sortdir=`ASC`&sortby=`menuindex`&limit=`0`&offset=`0`&rowIdPrefix=``&firstClass=`first`&lastClass=`last`&hereClass=`active`&parentClass=``&rowClass=``&outerClass=``&innerClass=``&levelClass=``&selfClass=``&webLinkClass=``&tplOuter=`@INLINE <ul[[+classes]]>[[+wrapper]]</ul>`&tpl=`@INLINE <li>{$pagetitle}</li>`&tplParentRow=``&tplParentRowHere=``&tplHere=``&tplInner=``&tplInnerRow=``&tplInnerHere=``&tplParentRowActive=``&tplCategoryFolder=``&tplStart=`@INLINE <h2[[+classes]]>[[+menutitle]]</h2>[[+wrapper]]`&checkPermissions=``&hereId=``&select=``&scheme=``&toPlaceholder=``&countChildren=``]]' 
       => 
    '<ul class=""><li>Главная</li><li>Вторая страница</li><li>Последняя страница</li></ul>',
  ),

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

Я выше привёл пример простого вызова сниппета pdoMenu. Как видим, условия для запроса передаются через специальные параметры сниппета («parents», «resources», «showDeleted» и т.п.). Но часто бывают запросы с более сложной логикой и через эти параметры её указать не получится. Для этого придётся задействовать параметр where, в котором напрямую указать предикаты запроса. А так как Fenom даёт возможность работать в PHP формате, то многие разработчики пользуются этой возможностью и используют массив вместо строки, так как xPDOQuery работает именно с массивом:

{'pdoMenu' | snippet : [
    'tpl' => '@INLINE <li>{$pagetitle}</li>',
    'where' => [
        'parent' => 0,
        'id:!=' => '-10',
    ]
]}

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

// core/cache/resource/web/resources/1.cache.php
'elementCache' => 
  array(
    '[[pdoMenu?&where=`cb618cc9874e2dc8afae8845b482d546`&showLog=``&fastMode=``&level=`0`&parents=`0`&displayStart=``&resources=`-10`&templates=``&context=``&cache=``&cacheTime=`3600`&cacheAnonymous=``&plPrefix=`wf.`&showHidden=``&showUnpublished=``&showDeleted=``&previewUnpublished=``&hideSubMenus=``&useWeblinkUrl=`1`&sortdir=`ASC`&sortby=`menuindex`&limit=`0`&offset=`0`&rowIdPrefix=``&firstClass=`first`&lastClass=`last`&hereClass=`active`&parentClass=``&rowClass=``&outerClass=``&innerClass=``&levelClass=``&selfClass=``&webLinkClass=``&tplOuter=`@INLINE <ul[[+classes]]>[[+wrapper]]</ul>`&tpl=`@INLINE <li>{$pagetitle}</li>`&tplParentRow=``&tplParentRowHere=``&tplHere=``&tplInner=``&tplInnerRow=``&tplInnerHere=``&tplParentRowActive=``&tplCategoryFolder=``&tplStart=`@INLINE <h2[[+classes]]>[[+menutitle]]</h2>[[+wrapper]]`&checkPermissions=``&hereId=``&select=``&scheme=``&toPlaceholder=``&countChildren=``]]' 
       => 
    '<ul class=""><li>Главная</li><li>Вторая страница</li><li>Последняя страница</li></ul>',
  ),

Обратите внимание на параметр where. Вместо данных, указанных в теге сниппета, он содержит случайный хэш.

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

Самое простое решение — вместо массива указывать строку в JSON формате. Тогда MODX сохранить всё как нужно. А pdoTools под капотом переведёт её в массив.

{'pdoMenu' | snippet : [
    'tpl' => '@INLINE <li>{$pagetitle}</li>',
    'where' => '{"parent":0,"id:!=":"-10"}'
]}

Теперь и вы об этом знаете. )

П.С. В следующей версии pdoTools я постараюсь исправить эту проблему.

0   3210

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

  1. Семён 26 августа 2022 # +1
    Прикольно! Спасибо, Сергей за статью! У меня куча кода с where в виде массивов, пошел переделывать)

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

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