MODX pdoTools — дополнение для быстрой выборки страниц и пользователей сайта

MODX pdoTools MODX Revo
В данном уроке разберем пожалуй самое важное и популярное дополнение для MODX, под название pdoTools.

Что такое pdoTools

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

Благодаря использованию общей библиотеки, все сниппеты pdoTools обладают единым минимальным функционалом:

  • Все запросы в БД работают через PDO. Объекты xPDO не создаются, только если они действительно не нужны.
  • Предварительная обработка простых плейсхолдеров в чанках. Парсер MODX разбирается только со сложными вызовами.
  • Правильная сортировка, подготовка, обработка и вывод ТВ параметров.
  • Код чанков можно указывать прямо при вызове сниппета, загружать обычным образом или из статичных файлов.
  • «Быстрые плейсхолдеры» в чанках, которые заменяют фильтры типа «isempty» и оборачивают значения в теги только если те не пусты.
  • Ведение подробного журнала работы сниппета с отметками времени, для отладки.

Все запросы строятся на xPDO, выборка производится через PDO для экономии ресурсов и скорости.

Основные возможности

  • Любые выборки, из любых таблиц с любыми условиями и джоинами.
  • Учет времени на каждую операцию, подробный лог для выявления узких мест.
  • Полная совместимость с getPage для постраничного вывода результатов.
  • Самый быстрый процессинг чанков, быстрее только вообще без них.
  • Встроенный шаблонизатор Fenom в версии 2.0

Установка pdoTools

Установка pdoTools стандартная, он доступен как в modx репозитории, так и в репозитории modxstore. Устанавливать рекомендую из репозитория modxstore — т.к. он первоисточник.

Сниппеты входящие в состав pdoTools

Послу установки дополнения во вкладке «Сниппеты» появится 8 дополнительных сниппетов, кратко разберем каждый из них.

pdoResources

pdoResources — предназначен для выборки и вывода ресурсов в любом оформлении на любой странице.

pdoPage

pdoPage — постраничный вывод результатов (выборка и вывод ресурсов с пагинацией), используется для вывода ресурсов в категория (блога, магазинов, услуг и т.д.).

pdoMenu

pdoMenu — строит меню различной сложности.

pdoCrumbs

pdoCrumbs — строит хлебные крошки.

pdoSitemap

pdoSitemap — быстрая генерация карты сайта (sitemap.xml).

pdoNeighbors

pdoNeighbors — вывод ссылок на соседние документы (например на предыдущую и следующую статью).

pdoField

pdoField — вывод любого поля документа (например, может вывести заголовок и ссылку родительского ресурса (категории)).

pdoUsers

pdoUsers — выборка и вывод пользователей сайта, с фильтрацией по ролям и группам.

pdoTitle

pdoTitle — выводит оформленный тег title на страницу сайта, который состоит из имени страницы, имён родителей этой страницы и, других параметров. Аля SEO title для страниц пагинации.

pdoArchive

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

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

Общие параметры pdoTools

Общие параметры для сниппетов, основанных на pdoTools/pdoFetch.

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

Эти параметры определяют, какие объекты будут получены.

Название По умолчанию Описание
&class modResource Класс получаемого объекта
&parents Текущий ресурс Список родителей, через запятую, для поиска результатов. Если поставить 0 — выборка не ограничивается. Если id родителя начинается с дефиса, он и его потомки исключаются из выборки.
&depth 10 Глубина поиска дочерних ресурсов от родителя.
&resources Список ресурсов, через запятую, для вывода в результатах. Если id ресурса начинается с дефиса, этот ресурс исключается из выборки.
&templates Список шаблонов, через запятую, для фильтрации результатов. Если id шаблона начинается с дефиса, ресурсы с ним исключается из выборки.
&context Ограничение выборки по контексту ресурсов.
&where Массив дополнительных параметров выборки, закодированный в JSON.
&showHidden 0 Показывать ресурсы, скрытые в меню.
&showUnpublished 0 Показывать неопубликованные ресурсы.
&showDeleted 0 Показывать удалённые ресурсы.
&hideContainers 0 Отключает вывод контейнеров, то есть, ресурсов с «isfolder = 1».
&hideUnsearchable Отключает вывод спрятанных от поиска ресурсов.
&select Список полей для выборки, через запятую. Можно указывать JSON строку с массивом, например {«modResource»:»id,pagetitle,content»}.
&leftJoin Аналог SQL оператора left join
&rightJoin Аналог SQL оператора right join
&innerJoin Аналог SQL оператора inner join
&joinSequence innerJoin,leftJoin,rightJoin Порядок подключения таблиц, через зяпятую.
&sortby pagetitle Любое поле ресурса для сортировки, включая ТВ параметр, если он указан в параметре &includeTVs. Можно указывать JSON строку с массивом нескольких полей. Для случайно сортировки укажите «RAND()»
&sortdir ASC Направление сортировки: по убыванию или возрастанию.
&groupby Указывает поле, по которому группируются результаты
&having Используется, чтобы ограничить выборку сгруппированных строк с помощью условия, относящегося ко всей группе, заданной в &groupby
&limit 0 Ограничение количества результатов выборки. Можно использовать «0».
&offset 0 Пропуск результатов от начала.
&first 1 Номер первой итерации вывода результатов.
&last Автоматически, по формуле (total + first — 1) Номер последней итерации вывода результатов.
&loadModels Список компонентов, через запятую, чьи модели нужно загрузить для построения запроса. Например: &loadModels=`ms2gallery,msearch2`.
&tvFilters Список фильтров по ТВ, с разделителями AND и OR. Разделитель, указанный в параметре &tvFiltersOrDelimiter представляет логическое условие OR и по нему условия группируются в первую очередь. Внутри каждой группы вы можете задать список значений, разделив их &tvFiltersAndDelimiter. Поиск значений может проводиться в каком-то конкретном ТВ, если он указан «myTV==value», или в любом «value». Пример вызова: &tvFilters=`filter2==one,filter1==bar%||filter1==foo`. Обратите внимание: фильтрация использует оператор LIKE и знак «%» является метасимволом. И еще: Поиск идёт по значениям, которые физически находятся в БД, то есть, сюда не подставляются значения по умолчанию из настроек ТВ.
&tvFiltersAndDelimiter «,» Разделитель для условий AND в параметре &tvFilters.
&tvFiltersOrDelimiter «||» Разделитель для условий OR в параметре &tvFilters.
&sortbyTV Дополнительное поле, по которому нужно сортировать результаты. Может быть указано напрямую в параметре &sortby
&sortdirTV Направление сортировки по дополнительному полю, указанному в &sortbyTV. Может быть указано напрямую в параметре &sortby
&sortbyTVType Тип сортировки по ТВ параметру. Возможные варианты: stringintegerdecimal и datetime. Если пусто, то ТВ будет отсортирован в зависимости от его типа: как текст, число или дата.
&checkPermissions Укажите, какие разрешения нужно проверять у пользователя при выводе объектов.
&disableConditions Отключает специфичные для класса modResource параметры выборки.
&fenomModifiers список сниппетов-модификаторов через запятую, для подключения в Fenom. Подробности в соответствующем разделе.

Параметры шаблонов

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

Название Описание
&tpl Имя чанка для оформления ресурса. Если не указан, то содержимое полей ресурса будет распечатано на экран.
&tplFirst Имя чанка для первого ресурса в результатах.
&tplLast Имя чанка для последнего ресурса в результатах.
&tplOdd Имя чанка для каждого чётного ресурса (хоть «odd» значит «нечётный», работает для чётных ресурсов).
&tpl_N Имя чанка для N-го ресурса, например, &tpl_4=`tpl4th` установит шаблон для 4-го ресурса.
&tpl_nN Имя чанка для каждого N-го ресурса, например, &tpl_n4=`tplEvery4th` будет применено к каждому 4-му ресурсу.
&tplCondition Поле ресурса, из которого будет получено значение для выбора чанка по условию в &conditionalTpls.
&tplOperator Необязательный оператор для проведения сравнения поля ресурса в &tplCondition с массивом значений и чанков в &conditionalTpls.
&conditionalTpls JSON строка с массивом, у которого в ключах указано то, с чем будет сравниваться &tplCondition, а в значениях — чанки, которые будут использованы для вывода, если сравнение будет успешно. Оператор сравнения указывается в &tplOperator. Для операторов типа isempty можно использовать массив без ключей.
&outputSeparator Необязательная строка для разделения результатов работы.

Параметры результатов

Эти параметры дополнительно определяют, какие данные и каким способом будут выводиться.

Название По умолчанию Описание
&return chunks Определяет способ вывода результатов. См. ниже.
&fastMode 0 Быстрый режим обработки чанков. Все необработанные теги (условия, сниппеты и т.п.) будут вырезаны.
&nestedChunkPrefix pdotools_ Префикс для «быстрых плейсхолдеров», включаемых параметром &fastMode
&idx Вы можете указать стартовый номер итерации вывода результатов.
&totalVar total Имя плейсхолдера для сохранения общего количества результатов.
&includeContent 0 Включаем поле «content» в выборку.
&includeTVs Список ТВ параметров для выборки, через запятую. Например: «action,time» дадут плейсхолдеры [[+action]] и [[+time]].
&includeTVList Псевдоним &includeTVs
&prepareTVs 1 Список ТВ параметров, с файлами из источников медиа, для которых нужно сгенерировать полные пути. Если установить в «1», будут подготовлены все ТВ, указанные в &includeTVs.
&processTVs Список ТВ параметров, которые нужно обработать и вывести согласно их настроек в менеджере системы. Если установить в «1», будут обработаны все ТВ, указанные в &includeTVs. Замедляет работу.
&tvPrefix tv. у pdoResources и пусто у других сниппетов Префикс для ТВ параметров.
&prepareSnippet 1 Указывает сниппет, который принимает данные перед выводом в чанк и может их менять или добавлять
&decodeJSON Разбирает поля типа JSON вместо вывода в виде строки
&scheme -1 Схема формирования url, передаётся в modX::makeUrl(), поэтому возможные варианты нужно смотреть здесь. Особый тип uri подставляет значение uri ресурса, без запуска функции.
&useWeblinkUrl Генерировать ссылку с учетом класса ресурса.
&toSeparatePlaceholders Если вы укажете слово в этом параметре, то ВСЕ результаты будут выставлены в разные плейсхолдеры, начинающиеся с этого слова и заканчивающиеся порядковым номером строки, от нуля. Например, указав в параметре «myPl», вы получите плейсхолдеры [[+myPl0]][[+myPl1]] и т.д.
&additionalPlaceholders Устанавливает дополнительные плейсхолдеры
&cache_key Значение системной настройки cache_resource_key для ресурсов (по умолчанию resource) или default Ключ кеширования
&cache_handler Значение системной настройки cache_resource_handler или xPDOFileCache Обработчик кеша
&cacheTime Значение системной настройки cache_resource_expires или 0 (вечный) Время жизни кеша (в секундах)

Способы вызова чанков

Все чанки могут иметь один из следующих префиксов:

@INLINE или @CODE. В качестве шаблона будет использован код после этого префикса.

[[!pdoResources?
    &parents=`0`
    &tpl=`@INLINE <li>{{+pagetitle}}</li>`
]]

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

Поэтому для INLINE чанков предусмотрена замена [[+]] на {{+}} — такие теги MODX пропускает, а pdoTools при работае конвертирует их как нужно. Конечно, вы всё равно можете использовать теги MODX, если вам нужно, чтобы в чанк попала уже обработанная информация, например:

[[!pdoResources?
    &parents=`0`
    &tplFirst=`@INLINE Текущая страница: [[*pagetitle]]`
    &tpl=`@INLINE <p>{{+id}} - {{+pagetitle}}<p>`
]]

@FILE. Вместо чанка из базы данных используется содержимое файла. Путь до файла указывается в систеной настройке pdotools_elements_path. Имя файла должно быть с расширением .tpl или .html.

[[!pdoResources?
    &tpl=`@FILE fileBasedRow.tpl`
]]

@TEMPLATE. Указывается идентификатор или имя шаблона. Если пусто — для каждого ресурса будет использован его собственный шаблон.

[[!pdoResources?
    &tpl=`@TEMPLATE 10`
]]

@CHUNK. Аналогично простому указанию имени чанка, оставлено для совместимости со сторонними сниппетами.

[[!pdoResources?
    &tpl=`@CHUNK tpl.Resource.row`
]]
[[!pdoResources?
    &tpl=`tpl.Resource.row`
]]

Подробнее про возможности pdoParser смотрите ниже.

Возвращаемые значения

pdoTools умеет возвращать данные в разном виде, в зависимости от параметр &return. В основном это используют сами сниппеты для внутренних нужд, но вы можете указывать &return в pdoResources:

[[!pdoResources?
    &parents=`0`
    &return=`json`
]]
  • chunks — оформленные чанки, по умолчанию.
  • sql — подготовленный сырой SQL, полезно для отладки. Сам запрос не выполняется, только выводится на экран.
  • data — готовый массив данных. Из-за особенностей работы сниппетов MODX этот вариант имеет смысл использовать только при вызове pdoFetch::run() напрямую из своего сниппета, в противном случае вы получите только строку «Array».
  • ids — возвращает только идентификаторы документов, через запятую. Удобно для подстановки в качестве параметра другим сниппетам. Параметр &returnIds использует именно этот тип.
  • json — возврат массива данных JSON строкой.
  • serialize — возврат массива данных сериализованной строкой. Иногда, по непонятным причинам, может вызвать нехватку памяти. Лучше использовать json.

Классы

Ядро компонента разделено на 3 класса: общий pdoTools, работа с БД — pdoFetch и работа с оформлением, то есть pdoParser (о них ниже).

При установке в систему они регистрируются таким образом, чтобы вы могли быстро их запускать:

$pdoTools = $modx->getService('pdoTools');
$pdoFetch = $modx->getService('pdoFetch');
$pdoParser = $modx->getService('pdoParser');

pdoFetch наследует pdoTools, так что не нужно вызывать эти два класса вместе. Если вы хотите работать с БД, вызывайте один Fetch, а если нет — Tools.

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

pdoTools

Основной класс компонента, который наследуют все остальные (кроме pdoParser — он наследует modParser).

Инициализация

Простая инициализация класса:

$pdoTools = $modx->getService('pdoTools');

Этот метод всегда вернёт оригинальный pdoTools.

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

$fqn = $modx->getOption('pdoTools.class', null, 'pdotools.pdotools', true);
if ($pdoClass = $modx->loadClass($fqn, '', false, true)) {
    $pdoTools = new $pdoClass($modx, $scriptProperties);
}
elseif ($pdoClass = $modx->loadClass($fqn, MODX_CORE_PATH . 'components/pdotools/model/', false, true)) {
    $pdoTools = new $pdoClass($modx, $scriptProperties);
}
else {
    $modx->log(modX::LOG_LEVEL_ERROR, 'Could not load pdoTools from "MODX_CORE_PATH/components/pdotools/model/".');
    return false;
}
$pdoTools->addTime('pdoTools loaded');

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

Ведение лога

Важная особенность pdoTools — он умеете вести лог того, что делает. Для этого вам доступны методы

  • addTime(string $message) — добавляет новую запись в лог
  • getTime(bool $string [true]) — добавляет итоговое время и возвращает либо отформатированную строку (по умолчанию), либо массив время => сообщение

Например, вот этот код:

$pdo = $modx->getService('pdoTools');
$pdo->addTime('pdoTools инициализирован');
print_r($pdo->getTime());

Выведет:

0.0000150: pdoTools инициализирован
0.0000272: Total time
1 572 864: Memory usage

То есть, вы можете подключать pdoTools в своих сниппетах, просто для логирования событий. Понятно дело, что его сниппеты сами всё пишут в лог, и как правило, менеджер может его почитать параметром &showLog=`1`.

Кэширование

pdoTools умеет кэшировать произвольные данные на время выполнения скрипта. Вы тоже можете этим пользоваться.

  • setStore(string $name, mixed $object, string $type [«data»]) — добавляет любые данные во временное хранилище.
  • getStore(string $name, string $type [«data»]) — получает или данные, или null.

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

foreach ($users as $id) {
    $user = $pdo->getStore($id, 'user')
    if ($user === null) {
        if (!$user = $modx->getObject('modUser', $id)) {
            $user = false;
        }
        $pdo->setStore($id, $user, 'user');
    }
    elseif ($user === false) {
        echo 'Не могу найти юзера с id = ' . $id;
    }
    else {
        echo $user->get('username');
    }
}

В этом коде мы сохраняем юзеров в отдельный namespace user, чтобы не мешать другим сниппетам, и проверяем наличие юзера в кэше. Обратите внимание, что по условиям примера, кэш может вернуть или null (юзер еще не получался), или false (юзер не найден). В любом случае, запрос в БД будет только один на каждого юзера.

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

Есть и более продвинутое кэширование, методами MODX:

  • setCache(mixed $data, array $options) — сохраняет данные $data в кэш, генерируя ключ из $options
  • getCache(array $options) — выдает данные, согласно $options

Здесь данные уже сохраняются на диск, время кэширования можно передавать в массиве параметров:

$pdo = $modx->getService('pdoTools');
$options = array(
    'user' => $modx->user->get('id'),
    'page' => @$_REQUEST['page'],
    'cacheTime' => 10,
);
$pdo->addTime('pdoTools загружен');
if (!$data = $pdo->getCache($options)) {
    $pdo->addTime('Кэш не найден, генерируем данные');
    $data = array();
    for ($i = 1; $i <= 100000; $i ++) {
        $data[] = rand();
    }
    $data = md5(implode($data));
    $pdo->setCache($data, $options);
    $pdo->addTime('Данные сохранены в кэш');
}
else {
    $pdo->addTime('Данные загружены из кэша');
}
print_r($data);

Таким образом, в зависимости от юзера и страницы будут получены какие-то данные и сохранены в кэш. Если зайдёт другой юзер — он получит свой кэш.

В первый раз наш код покажет примерно такое

0.0000281: pdoTools загружен
0.0004001: No cached data for key "default/e713939a1827e7934ff0242361c06b4b10c53d97"
0.0000079: Кэш не найден, генерируем данные
0.0581820: Saved data to cache "default/e713939a1827e7934ff0242361c06b4b10c53d97"
0.0000181: Данные сохранены в кэш
0.0586412: Total time
1 835 008: Memory usage

А затем вот такое:

0.0000310: pdoTools загружен
0.0007479: Retrieved data from cache "default/e713939a1827e7934ff0242361c06b4b10c53d97"
0.0000081: Данные загружены из кэша
0.0007918: Total time
1 572 864: Memory usage

Как видите, pdoTools и сам прекрасно пишет работу с кэшем в лог, так что вам можно это не логировать.

Утилиты

Здесь всего два метода.

  • &makePlaceholders(array $data, string $plPrefix, string $prefix [ ‘[[+’ ], string $suffix [ ‘]]’ ], bool $uncacheable [ true ]) Принимает массив ключ => значение и возвращает два массива плейсхолдеры => значения, используется для шаблонизации.

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

$data = array(
    'key1' => 'value1',
    'key2' => 'value2',
);

$pls = $pdo->makePlaceholders($data);
print_r($pls);

Результат:

Array
(
    [pl] => Array
        (
            [key1] => [[+key1]]
            [!key1] => [[!+key1]]
            [key2] => [[+key2]]
            [!key2] => [[!+key2]]
        )

    [vl] => Array
        (
            [key1] => value1
            [!key1] => value1
            [key2] => value2
            [!key2] => value2
        )

)

Дальше можно обработать какой-то html шаблон вот так:

$html = str_replace($pls['pl'], $pls['vl'], $html);
  • buildTree(array $resources) строит иерархическое дерево из массива ресурсов, используется pdoMenu.
$pdo = $modx->getService('pdoFetch');
$resources = $pdo->getCollection('modResource');
$tree = $pdo->buildTree($resources);
print_r($tree);

И вы увидите дерево ресурсов своего сайта. Обратите внимание, что для использования getCollection() нужно загружать pdoFetch.

Шаблонизация (работа с чанками)

Это, наверное, самая интересная часть класса pdoTools.

Метод здесь всего один — это getChunk(), однако вся его реализация рассчитана на максимальную производительность и функциональность.

Все плейсхолдеры в чанки, какие только может, обрабатывает pdoParser. Условие одно — плейсхолдер должен быть без условий и фильтров. То есть:

  • [[%tag]] — строка лексикона
  • [[~id]] — ссылка
  • [[+tag]] — обычные плейсхолдеры
  • [[++tag]] — системные плейсхолдеры
  • [[*tag]] — плейсхолдеры ресурса

Еще getChunk в pdoTools умеет работать с разными типами чанков:

  • @INLINE, @CODE — чанк создаётся из полученной строки.
  • @FILE — чанк получается из файла. Для исключения инъекций, файлы могут быть только с расширением html и tpl, а директория для их выборки задаётся системной настройкой pdotools_elements_path.
  • @TEMPLATE — чанк создаётся из шаблона сайта, можно указывать его id или имя.
  • @CHUNK или просто строка, без @префикса — выборка обычного чанка из БД.

Рабочий пример:

$tpl = '@INLINE <p>[[+param]] - [[+value]]</p>';
$res = '';
for ($i = 1; $i <= 10000; $i++) {
    $pls = array('param' =>$i, 'value' => rand());
    $res .= $pdo->getChunk($tpl, $pls);
}
print_r($pdo->getTime());
print_r($res);

Вот вам и простейшая шаблонизация при помощи pdoTools.

Этот код выводит 10 000 строк всего за 0.17 секунды! Причем, неважно, что чанк @INLINE, обычный работает с той же скоростью. А если заменить $pdo->getChunk() на $modx->getChunk(), то выходит уже 8 секунд!

То есть, в данном конкретном примере парсинг чанков MODX медленее pdoTools в 3000 раз — 8 секунд, против 0.17. Это говорит о том, что нужно максимально упрощать свои чанки, поменьше использовать условий и подключать pdoTools.

Чем же можно заменить условия? Самые простые «пусто\не пусто» заменяются «быстрыми плейсхолдерами». Работает это так:

  1. В чанке должен быть какой-то тег, например [[+tag]].
  2. В чанке должен быть специальный html комментарий в таком виде:
<!--pdotools_tag значение, если тег не пуст-->
<!--pdotools_!tag значение, если тег пуст, появилось только в версии 1.9.3-->

Как видите, комментарий именуется исходя из префикса pdotools_ и имени тега. Префикс меняется параметром &nestedChunkPrefix=«.

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

Пример:

$tpl = '@INLINE
    <p>[[+tag]]</p>
    <!--pdotools_tag [[+tag]] - значение, если тег не пуст-->
    <!--pdotools_!tag значение, если тег пуст, появилось только в версии 1.9.3, выпущенной сегодня-->
';

$pls = array('tag' => 1);
echo $pdo->getChunk($tpl, $pls);

$pls = array('tag' => 0);
echo $pdo->getChunk($tpl, $pls);

Получаем

1 - значение, если тег не пуст

значение, если тег пуст, появилось только в версии 1.9.3

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

Есть еще один интересный параметр обработки плейсхолдеров — &fastMode. Он выключает передачу плейсхолдеров в родной парсер MODX, и то, что не смог обработать pdoParser просто будет вырезано.

В последних версиях pdoTools его использовать нет нужды, потому что если pdoParser всё обработал, и в чанке не осталось ни одного [[+tag]], то он сразу отдаёт результат, не трогая modParser. Но вы можете его включить как принудительное требование для тех людей, которые меняют чанки — чтобы они не могли использовать трехэтажные конструкции.

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

pdoFetch

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

pdoFetch создаёт запрос и добавляет параметры через xPDO, а выбирает уже через PDO, что даёт защиту, гибкость и скорость.

Инициализация класса

Объект запроса xPDOQuery создаётся методом makeQuery(), исходя из переданных в pdoFetch параметров. По умолчанию, он работает с modResource, но если вам нужен другой класс — нужно его указать. При этом нужно учитывать, что метод modX::getService() всегда возвращает уже загруженный класс, если он есть. То есть, если вы (или какой-то сниппет) уже проинициализировали pdoFetch, то вы получите именно этот экземпляр класса, с уже установленными параметрами запроса.

Чтобы наверняка применить свои параметры нужно использовать pdoFetch::setConfig():

$pdo = $modx->getService('pdoFetch');
$pdo->setConfig(array('class' => 'modUser'));

Метод setConfig может сразу загружать указанные модели компонентов, чтобы вы могли работать с чужими дополнениями, например:

$pdo->setConfig(array(
    'class' => 'msResourceFile',
    'loadModels' => 'ms2gallery'
));

pdoTools не знает, какой класс принадлежит какому компоненту, поэтому нужно указать директорию дополнения в /core/components. В примере это компонент ms2Gallery, и директория его ms2gallery — оттуда и будет загружена модель для работы, о чем появится запись в логе.

После определения того, какие объекты мы будем выбирать, можно строить запрос. Мы можем делать это вручную, запуская все нужные методы по порядку, или выполнить pdoFetch::run(), и он сделает это за нас. Давайте пройдём по всей цепочке.

Метод makeQuery

Создаёт объект xPDOObject и добавляет его в приватное свойство класса pdoFetch.

Как мы выше уже выяснили, запрос создаётся для указанного класса, или для modResource, по умолчанию.

Основная цель pdoFetch — выбрать все данные за один запрос. Правда, если вы выбираете к ресурсу еще и ТВ параметры, то запросов приходится делать несколько.

Методы addTVFilters и addTVs

Эти два метода добавляют в запрос ТВ параметры и настраивают фильтрацию по ним.

Очевидно, что они работают только, если запрос строится для modResource и его наследников. У галереи нет ТВ, поэтому к ней эти методы не относятся.

Метод addTVFilters() преобразовывает параметр &tvFilters от getResources в понятные для pdoFetch параметры: where и includeTVs. То есть, сам он ничего особо не делает, он только разбирает чужой формат запроса и передаёт дальше.

Если вы умеете пользоваться параметром where, то tvFilters вам не нужен.

А вот метод addTVs() очень полезен — он подключает ТВ параметры, указанные списком в includeTVs. Чем больше вы укажете ТВ, тем больше нужно будет сделать join таблицы modTemplatevarContent.

В принципе, это не особо влияет на скорость, просто имейте в виду.

Метод addJoins

Этот метод получает параметры из конфига и отвечает за все подключения таблиц. Например, addTVs сам ничего не подключет, он только разбирает includeTVs и складывает попраметры в leftJoin.

Немного теории. Данные в реляционных БД хранятся в таблицах. Таблицы принято разделать согласно какой-то логике.

Например, есть сам юзер, а есть его профиль. Это две разные таблицы: modUser и modUserProfile. Как нам выбрать юзеров вместе с их профилями? Правильно, нужно присоединить таблицу одну к другой.

Теперь, давайте разберем типы присоединения:

  • leftJoin — выбираются все записи первой (левой) таблицы, даже если они не соответствуют записям во второй (правой) таблице. Этот способ используется для присоединения к ресурсу ТВ параметров, ибо они мы не знаем, есть они или нет, а вот ресурс нам нужно выбрать всегда.
  • rightJoin — здесь наоборот, Выбираются все записи второй (правой) таблицы, даже если они не соответствуют записям в первой (левой) таблице.
  • innerJoin — внутреннее объединение двух таблиц. Если для строки из одной таблицы нет соответствия в другой — такая строка исключается. То есть, innerJoin всегда возвращает данные сразу из двух таблиц.

Как нужно указывать параметры для присоединения в pdoFetch?

$pdo->setConfig(array(
    'class' => 'modUser',
    'leftJoin' => array(
        'Profile' => array(
            'class' => 'modUserProfile',
            'on' => 'modUser.id = Profile.internalKey',
        )
    ),
    'select' => array(
        'modUser' => '*',
        'Profile' => 'fullname,email',
    )
));

Итак, мы выбираем класс modUser, поэтому он сразу попадает в запрос. Затем мы указываем присоединить к нему класс modUserProfile под псевдонимом Profile. Присоединение производим по колонке id у modUser и internalKey у modUserProfile. А после мы указываем, какие столбцы в БД нам нужны для этих таблиц: для modUser выбираем все, а для Profile только полное имя и email.

Понятное дело, что при вызове сниппетов нельзя указывать массивы, поэтому в pdoResources наши параметры нужно превратить в JSON:

[[!pdoResources?
    &class=`modUser`
    &leftJoin=`{
        "Profile": {
            "class": "modUserProfile",
            "on": "modUser.id = Profile.internalKey"
    }
    }`
    &select=`{
        "modUser": "*",
        "Profile": "fullname,email"
    }`
    &showLog=`1`
    &sortby=`modUser.id`
    &sortdir=`ASC`
]]

И получаем вывод распечатанных массивов пользователей и вот такой лог pdoTools:

0.0000799: pdoTools loaded
0.0000319: xPDO query object created
0.0005212: leftJoined modUserProfile as Profile
0.0001311: Added selection of modUser: SQL_CALC_FOUND_ROWS `id`, `username`, `password`, `cachepwd`, `class_key`, `active`, `remote_key`, `remote_data`, `hash_class`, `salt`, `primary_group`, `session_stale`, `sudo`
0.0000789: Added selection of modUserProfile: `fullname`, `email`
0.0000360: Sorted by modUser.id, ASC
0.0000050: Limited to 10, offset 0
0.0000942: SQL prepared "SELECT SQL_CALC_FOUND_ROWS `modUser`.`id`, `modUser`.`username`, `modUser`.`password`, `modUser`.`cachepwd`, `modUser`.`class_key`, `modUser`.`active`, `modUser`.`remote_key`, `modUser`.`remote_data`, `modUser`.`hash_class`, `modUser`.`salt`, `modUser`.`primary_group`, `modUser`.`session_stale`, `modUser`.`sudo`, `Profile`.`fullname`, `Profile`.`email` FROM `modx_users` AS `modUser` LEFT JOIN `modx_user_attributes` `Profile` ON modUser.id = Profile.internalKey ORDER BY modUser.id ASC LIMIT 10 "
0.0019310: SQL executed
0.0001349: Total rows: 3
0.0000298: Rows fetched
0.0001581: Returning processed chunks
0.0034399: Total time
3 932 160: Memory usage

Сортировку в pdoResources нужно указывать потому, что там по умолчанию стоит publishedon, которой нет в выбираемых таблицах и мы получим ошибку, если не указать имеющееся поле.

Теперь, давайте разберем пример посложнее. Есть ресурс, а есть прикреплённые к нему файлы через платный компонент ms2Gallery. Ресурс один, а файлов несколько. Значит, на каждую запись в таблице ресурсов есть несколько записей в таблице галереи.

Выбираем ресурсы, прикрепляя к каждому по одной, первой картинке галереи вместе с превьюшкой 120х90:

[[!pdoResources?
    &parents=`0`
    &class=`modResource`
    &loadModels=`ms2gallery`
    &leftJoin=`{
        "Image": {
            "class": "msResourceFile",
            "on": "modResource.id = Image.resource_id AND Image.parent = 0"
        },
        "Thumb": {
            "class": "msResourceFile",
            "on": "Image.id = Thumb.parent AND Thumb.path LIKE '%120x90%'"
        }
    }`
    &select=`{
        "modResource": "*",
        "Image": "Image.url as image",
        "Thumb": "Thumb.url as thumb"
    }`
    &showLog=`1`
    &sortby=`id`
    &sortdir=`ASC`
]]

Здесь мы присоединяем одну таблицу msResourceFile 2 раза: первый раз как Image, а второй как Thumb. msResourceFile не родной класс MODX, о чем говорит его префикс ms, вместо mod — поэтому нам нужно подключить модель ms2gallery.

Дальше мы указываем как именно присоединять файлы. У превьюшек есть родитель, а у оригинальных картинок нет, значит нам нужно указать в условии Image.parent = 0. А вот превьюшки можно уже прицеплять не к ресурсу, а к их родителям, поэтому уловие для Thumb выглядит так:

Image.id = Thumb.parent

Ну и добавляем фильтрацию по размеру превьюшки — он прописывается в её адресе. Дальше мы указываем поля для выборки: у ресурса все, а у превьюшек только url под разными псевдонимами.

В итоге мы видим такой лог:

0.0001011: Loaded model "ms2gallery" from "/core/components/ms2gallery/model/"
0.0001070: pdoTools loaded
0.0000360: xPDO query object created
0.0003860: leftJoined msResourceFile as Image
0.0003152: leftJoined msResourceFile as Thumb
0.0002568: Added selection of modResource: SQL_CALC_FOUND_ROWS `id`, `type`, `contentType`, `pagetitle`, `longtitle`, `description`, `alias`, `link_attributes`, `published`, `pub_date`, `unpub_date`, `parent`, `isfolder`, `introtext`, `content`, `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.0000169: Added selection of msResourceFile: Image.url as image
0.0000129: Added selection of msResourceFile: Thumb.url as thumb
0.0000200: Processed additional conditions
0.0003581: Added where condition: modResource.published=1, modResource.deleted=0
0.0001092: Sorted by modResource.id, ASC
0.0000041: Limited to 10, offset 0
0.0002589: SQL prepared "SELECT SQL_CALC_FOUND_ROWS `modResource`.`id`, `modResource`.`type`, `modResource`.`contentType`, `modResource`.`pagetitle`, `modResource`.`longtitle`, `modResource`.`description`, `modResource`.`alias`, `modResource`.`link_attributes`, `modResource`.`published`, `modResource`.`pub_date`, `modResource`.`unpub_date`, `modResource`.`parent`, `modResource`.`isfolder`, `modResource`.`introtext`, `modResource`.`content`, `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`, Image.url as image, Thumb.url as thumb FROM `modx_site_content` AS `modResource` LEFT JOIN `modx_ms2_resource_files` `Image` ON modResource.id = Image.resource_id AND Image.parent = 0 LEFT JOIN `modx_ms2_resource_files` `Thumb` ON Image.id = Thumb.parent AND Thumb.path LIKE '%120x90%' WHERE  ( `modResource`.`published` = 1 AND `modResource`.`deleted` = 0 )  ORDER BY modResource.id ASC LIMIT 10 "
0.0015361: SQL executed
0.0000839: Total rows: 89
0.0001299: Rows fetched
0.0007241: Returning processed chunks
0.0049288: Total time
3 932 160: Memory usage

Как вы понимаете, можно смело указывать разные параметры для выборки ресурсов (&parents, &showHidden и т.д.), и к каждому из них будет прицеплено по 2 картинки, если есть.

Ну и последний пример с той же галерей, для закрепления. Теперь мы выберем все большие картинки с превьюшками 120х90 и названиями ресурсов к ним:

[[!pdoResources?
    &class=`msResourceFile`
    &loadModels=`ms2gallery`
    &where=`{
        "msResourceFile.parent": 0
    }`
    &leftJoin=`{
        "Resource": {
            "class": "modResource",
            "on": "msResourceFile.resource_id = Resource.id"
        },
        "Thumb": {
            "class": "msResourceFile",
            "on": "msResourceFile.id = Thumb.parent AND Thumb.path LIKE '%120x90%'"
        }
    }`
    &select=`{
        "msResourceFile": "*",
        "Resource": "pagetitle",
        "Thumb": "Thumb.url as thumb"
    }`
    &showLog=`1`
    &sortby=`id`
    &sortdir=`ASC`
]]

И получаем в итоге все картинки, к свойствам которых добавлено поле pagetitle от ресурса, и thumb с адресом превьюшки.

Лог работы:

0.0001011: Loaded model "ms2gallery" from "/core/components/ms2gallery/model/"
0.0000682: pdoTools loaded
0.0000339: xPDO query object created
0.0001562: leftJoined modResource as Resource
0.0001469: leftJoined msResourceFile as Thumb
0.0001209: Added selection of msResourceFile: SQL_CALC_FOUND_ROWS `id`, `resource_id`, `source`, `parent`, `name`, `description`, `path`, `file`, `type`, `createdon`, `createdby`, `rank`, `url`, `properties`, `hash`, `active`
0.0001709: Added selection of modResource: `pagetitle`
0.0000141: Added selection of msResourceFile: Thumb.url as thumb
0.0001609: Added where condition: msResourceFile.parent=0
0.0000348: Sorted by msResourceFile.id, ASC
0.0000038: Limited to 10, offset 0
0.0001800: SQL prepared "SELECT SQL_CALC_FOUND_ROWS `msResourceFile`.`id`, `msResourceFile`.`resource_id`, `msResourceFile`.`source`, `msResourceFile`.`parent`, `msResourceFile`.`name`, `msResourceFile`.`description`, `msResourceFile`.`path`, `msResourceFile`.`file`, `msResourceFile`.`type`, `msResourceFile`.`createdon`, `msResourceFile`.`createdby`, `msResourceFile`.`rank`, `msResourceFile`.`url`, `msResourceFile`.`properties`, `msResourceFile`.`hash`, `msResourceFile`.`active`, `Resource`.`pagetitle`, Thumb.url as thumb FROM `modx_ms2_resource_files` AS `msResourceFile` LEFT JOIN `modx_site_content` `Resource` ON msResourceFile.resource_id = Resource.id LEFT JOIN `modx_ms2_resource_files` `Thumb` ON msResourceFile.id = Thumb.parent AND Thumb.path LIKE '%120x90%' WHERE `msResourceFile`.`parent` = 0 ORDER BY msResourceFile.id ASC LIMIT 10 "
0.0007789: SQL executed
0.0001690: Total rows: 24
0.0000670: Rows fetched
0.0005300: Returning processed chunks
0.0032029: Total time
3 932 160: Memory usage

Как видите, ничего сложного, если понимать, что делаешь.

Метод addGrouping

Этот метод добавляет группировку в запрос. Он нужен только если вы используете Join, и одной строке левой таблицы соответствует несколько строк правой.

Обычно группировка нужна для подсчета количества правых строк. например, давайте выведем количество больших картинок галереи для каждого ресурса:

[[!pdoResources?
    &parents=`0`
    &class=`modResource`
    &loadModels=`ms2gallery`
    &leftJoin=`{
        "Image": {
            "class": "msResourceFile",
            "on": "modResource.id = Image.resource_id AND Image.parent = 0"
        }
    }`
    &select=`{
        "modResource": "*",
        "Image": "COUNT(Image.id) as images"
    }`
    &groupby=`modResource.id`
    &showLog=`1`
    &sortby=`id`
    &sortdir=`ASC`
]]

Если в этом запросе мы не добавим группировку по modResource.id, то получим ровно один результат, потому что MySql решит, что мы считаем общее количество картинок для всех строк и выведет нам только один результат.

С группировкой же, картинки считаются для каждой строки modResource и всё выводится правильно — мы получаем поле images с общим количество больших картинок для ресурса.

Если логика не ясна, почитайте про то, как MySql работает с запросами.

Метод addSelects

Выше в примерах мы уже видели применение select, а теперь нужно сказать пару слов о правилах выборки полей:

  • Если вы не делаете Join, то select можно указывать строкой полей
&select=`id,pagetitle,longtitle`
  • При использовании Join нужен массив, который укажет из какой таблицы что выбирать
&select=`{
    "modResource": "id,pagetitle,longtitle"
    "Image": "url"
}
  • Если используются псевдонимы в select, то обязательно нужно указать и псевдоним таблицы
&select=`{
    "modResource": "id,pagetitle,longtitle"
    "Image": "Image.url as image"
}
  • Перечисление всех полей можно заменять звездочкой
&select=`{
    "modResource": "*"
    "Image": "Image.url as image"
}

Все возможные ошибки Join, Select и Group смотрите в логе pdoTools — там всё пишется.

Метод addWhere

Это очень важный метод, который берет параметр &where, разбирает его и добавляет условия выборки в запрос.

Задаётся он так же, массивом:

&where=`{"id:>": "15", "published:!=": "0"}`

Если вы подключили какие-то таблицы в выборку, то можно фильтровать по ним:

&leftJoin=`{
    "Image": {
        "class": "msResourceFile",
        "on": "modResource.id = Image.resource_id AND Image.parent = 0"
    }
}`
&where=`{
    "published:!=": "0",
    "Image.active": "1"
}`

Если у полей таблицы могут быть одинаковые столбцы (обычно это id), то нужно указывать имя колонки с таблицей:

&where=`{
    "modResource.id:>": "15",
    "published:!=": "0",
    "Image.active": "1"
}`

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

&includeTVs=`image,file,mytv`
&where=`{
    "image:LIKE":"%yandex.ru%",
    "OR:file:=": "1",
    "OR:text:!=": "",
}`

Но если вдруг автозамена не сработает, то по ТВ нужно фильтровать вот так:

&includeTVs=`image,file,mytv`
&where=`{
    "TVimage.value:LIKE":"%yandex.ru%",
    "OR:TVfile.value:=": "1",
    "OR:TVtext.value:!=": "",
}`

Как видите, ТВ присоединияются как TVимятв и фильтровать нужно по значению value.

А что делать, если не получается указать нужное нам уловие при помощи массива для xPDO? Тогда нужно указывать массив с одной строкой с чистым SQL:

&where=`["
    TVimage LIKE '%yandex%' OR (TVfile.value = 1 AND TVtext.value != '')
"]

Обратите внимание на квадратные скобочки, вместо фигурных. Такое SQL условие попадет в запрос без дополнительных обработок, так что смотрите в лог на предмет ошибок.

Быстро проверить правильность JSON строк можно здесь.

Метод addSort

Этот метод добавляет в запрос сортировку и умеет принимать как строку, так и массив. В случае простой сортировки по одному полю нужно указать два параметра:

&sortby=`publishedon`
&sortdir=`ASC

Если в запросе есть Join, то лучше указывать с именем таблицы:

&sortby=`modResource.publishedon`
&sortdir=`ASC

А если нужна сортировка по нескольким полям, тогда нужно указать массив поле => направление:

&sortby=`{
    "modResource.publishedon": "ASC",
    "modUser.id": "DESC"
}`

Конечно, не забываем присоединять таблицы через Join, если планируете сортировать по ним.

Выполнение запроса

Дальше запрос подготавливается методом prepareQuery, и выполняется через PDO.

У сниппетов pdoFetch есть еще один параметр &return=«, который определяет, что вернет метод run():

  • sql — строка с готовым SQL запросом, сам он не выполняется.
  • ids — список с id подходящих объектов, через запятую. Обычно используется, чтобы выбрать одним сниппетом нужные id ресурсов и передать их на вход другому сниппету.
  • data — массив с результатами. При вызове через сниппет вы получите слово Array, так как все сниппеты MODX возвращают только строки. Зато при вызове из другог осниппета вы получите массив.
  • tpl — результат запроса, оформленный в указанный чанк &tpl=«. А если чанка нет, то просто распечатанные массивы результатов.

У сниппетов по умолчанию &return = tpl, а если есть параметр &returnIds, то idsData и sql в сниппетах не используется. Первый просто не работает, а второй вы и так видите в логе pdoTools.

Обработка результатов

После выполнения запроса метод run() вызывает еще один интересный метод prepareRows() — его цель прогнать результаты и подготовить для вывода.

Именно этот метод отвечает за раскодировку JSON полей и подготовку ТВ, если указаны &prepareTVs=« или &processTVs=«.

Также вызывается еще checkPermissions() — он отвечает за проверку разрешений, указанных в одноименном параметре. Учтите, что для проверки разрешений приходится создавать объекты xPDO, что замедляет работу, поэтому используйте этот параметр только если он реально необходим:

&checkPermissions=`list,view`

Ну а дальше уже результаты отдаются в виде массива данных или оформленных чанков.

Полезные методы getArray и getCollection

Эти два метода нужны для быстрой замены родным методам MODX: getObject и getCollection. В отличии от них, они возвращают массивы, а не объекты.

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

$pdo = $modx->getService('pdoFetch');
$res = $pdo->getArray('modResource', 1);
print_r($res);

или

$pdo = $modx->getService('pdoFetch');
$resources = $pdo->getCollection('modResource');
foreach ($res as $res) {
    print_r($res);
}

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

$pdo = $modx->getService('pdoFetch');
$res = $pdo->getArray('modResource', 1);

print_r($modx->getPlaceholder('pdoTools.log'));

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

$files = $pdo->getCollection('TicketFile', array('parent' => 1));
print_r($files);

А вот так — вывод всех файлов тикетов с присоединением к ним pagetitle родительского документа:

$files = $pdo->getCollection('TicketFile', array(), array(
    'innerJoin' => array(
        'Ticket' => array(
        'class' => 'Ticket',
            'on' => 'Ticket.id = TicketFile.parent'
        )
    ),
    'select' => array(
        'TicketFile' => '*',
        'Ticket' => 'pagetitle'
    ),
    'sortby' => array(
        'TicketFile.id' => 'ASC'
    )
));
print_r($modx->getPlaceholder('pdoTools.log'));
print_r($files);

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

Лог получается вот такой:

0.0000322: xPDO query object created
0.0003891: innerJoined Ticket as Ticket
0.0001400: Added selection of TicketFile: SQL_CALC_FOUND_ROWS `id`, `parent`, `class`, `source`, `name`, `description`, `path`, `file`, `type`, `size`, `createdon`, `createdby`, `url`, `thumb`, `deleted`, `properties`, `hash`
0.0001042: Added selection of Ticket: `pagetitle`
0.0005560: SQL prepared "SELECT SQL_CALC_FOUND_ROWS `TicketFile`.`id`, `TicketFile`.`parent`, `TicketFile`.`class`, `TicketFile`.`source`, `TicketFile`.`name`, `TicketFile`.`description`, `TicketFile`.`path`, `TicketFile`.`file`, `TicketFile`.`type`, `TicketFile`.`size`, `TicketFile`.`createdon`, `TicketFile`.`createdby`, `TicketFile`.`url`, `TicketFile`.`thumb`, `TicketFile`.`deleted`, `TicketFile`.`properties`, `TicketFile`.`hash`, `Ticket`.`pagetitle` FROM `modx_tickets_files` AS `TicketFile` JOIN `modx_site_content` `Ticket` ON Ticket.id = TicketFile.parent "
0.0023780: Total time
1 835 008: Memory usage

Как видите, эти методы очень удобны для использования в своих сниппетах. На них можно переложить основную работу по добыче данных из БД, а если вспомнить еще про pdoTools::getChunk() — то и оформлению.

pdoParser

pdoParser является заменой класса modParser.

Обработка плейсхолдеров

Его задача — стараться быстро разобрать теги MODX без создания объектов, как это делает оригинальный парсер. pdoParser умеет работать только с простыми тегами, без фильтров и условий, то есть:

  • [[%tag]] — строка лексикона
  • [[~id]] — ссылка
  • [[+tag]] — обычные плейсхолдеры
  • [[++tag]] — системные плейсхолдеры
  • [[*tag]] — плейсхолдеры ресурса
  • [[#tag]] — плейсхолдеры FastField

Он умеет:

  • Выводить поля ресурсов: [[#7.pagetitle]][[#11.content]]
  • Выводить ТВ параметры ресурсов: [[#5.date]][[#23.some_tv]]
  • Выводить поля товаров miniShop2: [[#11.price]][[#12.article]]
  • Выводить массивы ресурсов и товаров: [[#11.properties.somefield]][[#14.size.1]]
  • Выводить глобальные массивы: [[#POST.key]][[#SESSION.another_key]]
  • Распечатывать массивы для отладки: [[#12.colors]][[#GET]][[#14.properties]]

Цифра после решетки — это id ресурса, от которого нужно выбрать данные.

Все эти теги pdoTools обрабатывает без создания объектов modElement, поэтому работает немного быстрее чем родные методы MODX. Если же плейсхолдер вызван с какими-то параметрами, то он уйдёт в родной modParser.

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

С версии 2.0 в состав pdoTools входит шаблонизатор Fenom.

Работает только при включенном pdoParser и если разрешен в системных параметрах.

Возможности
  • Компилируется в нативный PHP код, который выполняется гораздо быстрее, чем теги MODX. Прирост, в среднем 30% — 50%.
  • Может работать и в чанках и на страницах сайта
  • Теги Fenom и MODX никак не мешают друг другу и работают одновременно
  • Если в чанке нет плейсхолдеров MODX, то парсер MODX не запускается
  • Если в чанке нет тегов Fenom, то он тоже не запускается
  • Поддерживаются даже @INLINE чанки
  • В отличии от других решений, вам не нужно никаким образом менять или переписывать свои сниппеты — всё работает через методы pdoTools::getChunk() и pdoTools::parseChunk() автоматически.
  • Все ошибки компиляции пишутся в системный журнал
Настройки
  • pdotools_fenom_default включает обработку синтаксиса Fenom во всех чанках сайта.
  • pdotools_fenom_parser включает обработку синтаксиса Fenom на страницах сайта. Контент ресурсов, шаблоны — везде. По умолчанию отключено.
  • pdotools_fenom_php включает возможность выполнения произвольных функций PHP в шаблонах через {$.php.функция()}. Опция эта очень опасная, так что тоже отключена.
  • pdotools_fenom_modx — чуть менее опасная опция, но во многих случаях, пока, необходимая — работа с объектами modX и pdoTools через переменные {$modx} и {$pdoTools}. Если вы не доверяете своим менеджерам — выключите её от греха подальше, потому что через объект modX можно удалить начисто весь сайт.
  • pdotools_fenom_cache — включает кэшированние чанков (только чанков, не страниц сайта) через кэшер MODX (а не как раньше). Стоит использовать только на продакшн сайтах при больших и сложных чанках.
Порядок запуска шаблонизатора

Если включен pdoParser и системная опция pdotools_fenom_parser, то шаблонизатор запускается ровно вот здесь.

В этот момент все кэшированные чанки и сниппеты на странице обработаны (или загружены из кэша) и вы можете использовать вот такие конструкции:

{if $.get.test == 1}
    [[!pdoResources?parents=`0`]]
{else}
    [[!pdoMenu?parents=`0`]]
{/if}

То есть, в зависимости от $_GET['test'] на странице будет запущен или один сниппет или другой. Парсер MODX же запустил бы оба и результат выполнения одного неподходящего просто не показал.

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

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

Кэширование чанков Fenom

По умолчанию этот функционал Fenom отключен, потому что по моим тестам, толку от него в MODX нет. Но, это на моих мелких и простых чанках, а у вас может быть что-то посложнее.

Поэтому вы можете включить системную настройку pdotools_fenom_cache и тогда скомпилированные шаблоны будут сохранены в /cache/default/fenom/ в зависимости от своего типа.

Чанки из БД кэшируются под своими id, а INLINE именуются как хэш от своего содержимого, то есть — путь к обычному чанку будет выглядеть как cache/default/fenom/chunk/90.cache.php, а к INLINE уже как cache/default/fenom/inline/35e115c27fdc3814b6f41a1015aa67e6.cache.php.

Отсюда следует, что нормальные чанки из БД кэшируются намертво, и обновляются только при очистке системного кэша, а INLINE чанки при изменении контента сохраняются под новым именем и весь кэш чистить не нужно.

Как это работает дальше?

При первом запуске с пустым кэшем pdoTools получает нужный чанк, определяет его тип и отдаёт в Fenom. Тот компилирует шаблон и сохраняет его во внутренний кэш pdoTools методом setStore(). Этот кэш находится в ОЗУ и сохраняется только на время выполнения скрипта, он нужен чтобы не компилировать 10 раз один и тот же чанк при выводе pdoResources.

А вот если включена опция pdotools_fenom_cache, то исходный код скомпилированного шаблона сохраняется на HDD сервера, и при следующем запуске Fenom уже не нужно его компилировать. Кэшер MODX отдаёт исходный код, из него получается объект Fenom\Render который передаётся в setStore() и оттуда уже работает.

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

Обычно выходит, что на маленьких и простых чанках (как у сниппетов pdoTools) выигрыша нет, а лишних файлов много, а вот на больших и сложных чанках (которые вы наверняка создадите, используя возможности Fenom) разница уже может быть. Время компиляции и работы с кэшем выводится в &showLog=`1`, так что каждый может проверить сам.

Примеры

Стандартный чанк tpl.Tickets.comments.wrapper из компонента Tickets

<div class="comments">
    [[+modx.user.id:isloggedin:is=`1`:then=`
    <span class="comments-subscribe pull-right">
        <label for="comments-subscribe" class="checkbox">
            <input type="checkbox" name="" id="comments-subscribe" value="1" [[+subscribed:notempty=`checked`]] />
            [[%ticket_comment_notify]]
        </label>
    </span>
    `:else=``]]

    <h4 class="title">[[%comments]] (<span id="comment-total">[[+total]]</span>)</h4>

    <div id="comments-wrapper">
        <ol class="comment-list" id="comments">[[+comments]]</ol>
    </div>

    <div id="comments-tpanel">
        <div id="tpanel-refresh"></div>
        <div id="tpanel-new"></div>
    </div>
</div>

Он же, переписанный для работы с Fenom

<div class="comments">
    {if $modx->user->isAuthenticated($modx->context->key)}
        <span class="comments-subscribe pull-right">
        <label for="comments-subscribe" class="checkbox">
            <input type="checkbox" name="" id="comments-subscribe" value="1" {$subscribed != '' ? 'checked' : ''} />
            {$modx->lexicon('ticket_comment_notify')}
        </label>
    </span>
    {/if}

    <h4 class="title">{$modx->lexicon('comments')} (<span id="comment-total">{$total}</span>)</h4>

    <div id="comments-wrapper">
        <ol class="comment-list" id="comments">{$comments}</ol>
    </div>

    <div id="comments-tpanel">
        <div id="tpanel-refresh"></div>
        <div id="tpanel-new"></div>
    </div>
</div>

Источник
Поделиться с друзьями
Алексей

Веб-дизайнер и SEO оптимизатор. Занимаюсь созданием сайтов с 2010 года и их продвижение с 2012 года!

Оцените автора
( 1 оценка, среднее 5 из 5 )
Web-Revenue.ru
Добавить комментарий