Сегодня поговорим о популярной библиотеке pdoTools. Она предоставляет полный набор необходимых для разработки сайта сниппетов. Для их использования не нужны знания программирования. Если возможностей этих сниппетов не хватает, то можно создать своё решение, используя готовые классы pdoTools и pdoFetch. Библиотека позволяет решить практически все задачи. Но и у неё есть ограничения. Для использования на больших сайтах с высокой посещаемостью её необходимо оптимизировать. Основные задачи, с которыми вы можете столкнуться —

  • Оптимизация SQL запроса.
  • Компиляция файлов шаблонизатора Fenom на каждый запрос.

Оптимизация SQL запроса

Так как карта ресурсов теперь неполная или вообще отключена, то основные сниппеты не смогут выводить ресурсы указанных родителей. Причина в том, что pdoTools использует метод modX::getChildIds(), который получает id дочерних ресурсов из карты ресурсов. А она неполная. Чтобы выкрутиться из этой ситуации, придётся изменить SQL запрос. Сделать это можно двумя способами.

Предупреждение!

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

1 способ

Этот способ самый простой. Нужно добавить джойн таблицы ресурсов, в котором определить родителей. Причём указать их нужно в самом джойне, а в параметре parents нужно оставить 0.

// Один уровень вложенности
{'!pdoResources' | snippet : [
    'parents'=>'0',
    'innerJoin' => [
        "Parents" => [
            "class" => "modResource",
            "on" => "`modResource`.`parent` = `Parents`.`id` AND `Parents`.`id` IN (1,2,3,4,5)",
        ]
    ],
    'tpl'=>'@INLINE <p>{$pagetitle}</p>',
    ...
]}
// Два уровня вложенности
{set $table = $modx->getTableName('modResource')} 
{'!pdoResources' | snippet : [
    'parents'=>'0',
    'innerJoin' => [
        "Parents" => [
            "class" => "modResource",
            "on" => "`modResource`.`parent` = `Parents`.`id` AND `Parents`.`id` IN (SELECT `id` FROM {$table} WHERE `id` IN (1,2,3,4,5) OR `parent` IN (1,2,3,4,5))",
        ]
    ],
    'tpl'=>'@INLINE <p>{$pagetitle}</p>',
    ...
]}

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

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

2 способ

Этот способ повышает производительность примерно на 15%. Замеры производились на сложных запросах, в которых использовались TV. А значит было много джойнов. Возможно на простых запросах эффективность этого способа будет не так заметна.

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

Далее его нужно подключить. В системных настройках pdoTools находим настройку pdoFetch.class и указываем в ней значение «pdotools.pdofetchopt». Теперь можно использовать дополнительный параметр from у сниппетов.

{'!pdoResources' | snippet : [
    'parents'=>'0',
    'from' => '(SELECT * FROM modx_site_content WHERE parent IN (1,2,3,4,5))',
    'limit' => 10,
    'tpl'=>'@INLINE <p>{$pagetitle}</p>',
]}

Получается вот такой лог

0.0000000: pdoTools loaded
0.0000000: xPDO query object created
0.0050001: Added selection of modResource: `id`, `type`, `contentType`, `pagetitle`, `longtitle`, `description`, `alias`, `alias_visible`, `link_attributes`, `published`, `pub_date`, `unpub_date`, `parent`, `isfolder`, `introtext`, `richtext`, `template`, `menuindex`, `searchable`, `cacheable`, `createdby`, `createdon`, `editedby`, `editedon`, `deleted`, `deletedon`, `deletedby`, `publishedon`, `publishedby`, `menutitle`, `donthit`, `privateweb`, `privatemgr`, `content_dispo`, `hidemenu`, `class_key`, `context_key`, `content_type`, `uri`, `uri_override`, `hide_children_in_tree`, `show_in_tree`, `properties`
0.0000000: Added FROM data: (SELECT * FROM modx_site_content WHERE parent IN (1,2,3,4,5))
0.0000000: Processed additional conditions
0.0000000: Added where condition: modResource.published=1, modResource.deleted=0
0.0000000: Sorted by modResource.publishedon, DESC
0.0000000: Limited to 10, offset 0
0.0000000: SQL prepared "SELECT `modResource`.`id`, `modResource`.`type`, `modResource`.`contentType`, `modResource`.`pagetitle`, `modResource`.`longtitle`, `modResource`.`description`, `modResource`.`alias`, `modResource`.`alias_visible`, `modResource`.`link_attributes`, `modResource`.`published`, `modResource`.`pub_date`, `modResource`.`unpub_date`, `modResource`.`parent`, `modResource`.`isfolder`, `modResource`.`introtext`, `modResource`.`richtext`, `modResource`.`template`, `modResource`.`menuindex`, `modResource`.`searchable`, `modResource`.`cacheable`, `modResource`.`createdby`, `modResource`.`createdon`, `modResource`.`editedby`, `modResource`.`editedon`, `modResource`.`deleted`, `modResource`.`deletedon`, `modResource`.`deletedby`, `modResource`.`publishedon`, `modResource`.`publishedby`, `modResource`.`menutitle`, `modResource`.`donthit`, `modResource`.`privateweb`, `modResource`.`privatemgr`, `modResource`.`content_dispo`, `modResource`.`hidemenu`, `modResource`.`class_key`, `modResource`.`context_key`, `modResource`.`content_type`, `modResource`.`uri`, `modResource`.`uri_override`, `modResource`.`hide_children_in_tree`, `modResource`.`show_in_tree`, `modResource`.`properties` FROM (SELECT * FROM modx_site_content WHERE parent IN (1,2,3,4,5)) AS `modResource` WHERE  ( `modResource`.`published` = 1 AND `modResource`.`deleted` = 0 )  ORDER BY modResource.publishedon DESC LIMIT 10 "
0.0000000: SQL executed
0.0000000: Rows fetched
0.0000000: Created inline "modChunk" with name "294ced7c63d3421f4f42aef9c0108b40"
0.0000000: Retrieved data from cache "default/pdotools/modchunk/294ced7c63d3421f4f42aef9c0108b40"
0.0049999: Returning processed chunks
0.0100000: Total time
2 097 152: Memory usage

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

Проблема карты ресурсов

Если вы решили всё-таки не трогать карту ресурсов, то у вас может возникнуть другая проблема. Представьте сайт, у которого несколько главных разделов, и уровень вложенности дочерних ресурсов у них разный. Например, для новостей используются подразделы (Интересное, Политика, Проишествия, Спорт, Музыка и т.д.), а в разделах «Статьи» и «Культурные мероприятия» лишний уровень не нужен. Таким образом, сами новости находятся на третьем уровне, а статьи и культурные мероприятия на втором. Когда на главной странице нужно вывести в одну ленту событий данные из этих разделов, то, как правило, используют сниппет pdoResources, у которого в параметре parents перечисляют id нужных разделов. И обычно их единицы. Но если посмотреть готовый SQL запрос, то окажется, что количество id родителей значительно выросло.

Это происходит потому, что сниппет не ограничивается только указанными родителями. Он выводит все дочерние ресурсы на указанную глубину, которая по-умолчанию имеет значение 10. Для небольших и средних сайтов решение вполне безобидное. Но для больших сайтов это чревато тем, что перечень этих id будет содержать десятки, а может и сотни тысяч единиц. MySql придётся дробить запросы, а потом склеивать полученные результаты. Это, как вы понимаете, отрицательно влияет на производительность. Оба вышеописанных способа позволяют решить и эту проблему.

Параметр «depth» сниппетов pdoTools

Я выделил это в отдельный пункт по причине значимости. Этот параметр используется в некоторых сниппетах pdoTools. Дело в том, что им не всегда пользуются правильно. В описании про него написано следующее:

Глубина поиска дочерних ресурсов от родителя.

Я встречал ситуации, когда глубину считали от уровня всего сайта, а не от указанных родителей. Но гораздо чаще сталкивался с нумерацией от родителей, где для вывода непосредственных дочерних элементов указывалось значениe 1, для следующего уровня значение 2, и т.д. Так вот, несмотря на очевидность, это решение неправильное, так как счёт идёт от нуля. Т.е. чтобы вывести 2 уровня ресурсов указанных родителей, нужно указать значение 1.

Компиляция файлов шаблонизатора Fenom на каждый запрос

Про недоработанный механизм кэширования элементов библиотеки pdoTools я уже много раз говорил. Повторяться не буду. Сейчас мы раскроем другую проблему, влияющую на производительность. Разговор пойдёт про компиляцию и кэширование самого шаблонизатора Fenom. В pdoTools кэшированием распарсенного контента управляет системная настройка pdotools_fenom_cache (Кэширование скомпилированных чанков). У многих она выключена, что заставляет Fenom на каждый запрос заново компилировать не только чанки, но и контент страницы (хотя в названии говорится только про чанки). А если её включить, то на больших сайтах можно получить сотни тысяч файлов в кэше. Т.е. практически на каждую страницу. Многие поэтому и отключают. Но такое возможно только при неправильной разметке, когда в контенте самого ресурса размещается логика Fenom. Если убрать всю логику в шаблон, а в контенте оставить только текст и теги MODX, то для Fenom все страницы с таким шаблоном будут выглядеть одинаково. А значит и файл кэша будет только один.

Заключение

Возможно данные решения пригодятся не всем. Возможно кто-то решит эти задачи по своему. Главное — знать о проблемах, с которыми вы можете столкнуться на больших сайтах. Но на этом тема про pdoTools ещё не закрыта. Есть ещё ряд задач, которые предстоит решить. Одна из них — пагинация. Но об этом поговорим в следующей статье.

0   3077

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

  1. Александр 03 июля 2021 # 0
    Супер:v
    1. Sentinel 07 июля 2021 # 0
      А как сделать такое для minishop2 и категорий?
      1. Сергей Шлоков 08 июля 2021 # 0
        Теоретически также. Принципиальных отличий не вижу.
        1. Sentinel 08 июля 2021 # 0
          а должен быть class msCategory или msProduct?
          что-то не работает у меня:r
          1. Сергей Шлоков 08 июля 2021 # 0
            Ты меня подловил. Я не знаю. ) Я с минишопом не знаком от слова «совсем».
            1. Sentinel 09 июля 2021 # 0
              Было бы классно, если бы ты взглянул на это дело, т.к. зачастую проблемы с нагрузкой связаны с вызовом msProducts или mFilter2, большим кол-вом товаров и опций из каталога:E

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

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