Большой рассказ про pdoTools, часть первая

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

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

Понятное дело, что через какое-то время я пришел к универсальным сниппетам "на все случаи жизни", которые и вошли в комплект pdoTools. Про них вы можете почитать на страницах документации, а ниже я расскажу, что же там под капотом.

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

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

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


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

pdoFetch наследует pdoTools, так что не нужно вызывать эти два класса вместе. Если вы хотите работать с БД, вызывайте один Fetch, а если нет - Tools. Парсер вызывать вообще не нужно, они или включен в настройках MODX, или нет. 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"])

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


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

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


$pdo = $modx->getService('pdoTools', array(
    'cache_key' => 'resource',
    'cache_handler' => 'xPDOFileCache',
    'cacheTime' => 10,
));

Первые 2 параметра обычно не нужны, нас интересует только время жизни - cacheTime.

Пример:


$pdo = $modx->getService('pdoTools');
$options = array(
    'user' => $modx->user->get('id'),
    'page' => @$_REQUEST['page'],
);
$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]] - плейсхолдеры ресурса

  • [[#tag]] - плейсхолдеры FastField

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

  • Выводить поля ресурсов: [[#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]]

  • Распечатывать массивы для отладки: [[#15.colors]], [[#GET]], [[#12.properties]]

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

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

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

  • @INLINE, @CODE - чанк создаётся из полученной строки.

  • @FILE - чанк получается из файла. Для исключения инъекций, файлы могут быть только с расширением html и tpl, а директория для их выборки задаётся параметром $tplPath=`` в конфиге. По умолчанию, чанки выбираются из MODX_ASSETS_PATH . 'elements/chunks/'.

  • @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('test', $pls);;
}
echo '<pre>';
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. Но вы можете его включить как принудительное требование для тех людей, которые меняют чанки - чтобы они не могли использовать трехэтажные конструкции.

Заключение

Рассказ про pdoTools пришлось разбить на две части, потому что объём выходит немаленький.

В следующей серии я расскажу вам про pdoFetch и как с его помощью выбирать что угодно на сайте.

← Предыдущая заметка
Базовые дополнения: Theme.Bootstrap, MinifyX и немного pdoTools
Следующая заметка →
Большой рассказ про pdoTools, часть вторая
Комментарии (18)
Наумов Алексей
29.07.2014 10:08
<!--!pdotools_tag значение, если тег пуст, появилось только в версии 1.9.3, выпущенной сегодня-->

За это спасибо, ждал-ждал!

bezumkinВасилий Наумкин
29.07.2014 10:55

Ай, очепятка. Правильный тег вот такой:

<!--pdotools_!tag значение -->

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

kondakovДмитрий Кондаков
30.07.2014 11:13

Василий, а можешь рассмотреть в уроках такую нестандартную ситуацию: выборка файлов в тикетах не используя TicketMeta?

bezumkinВасилий Наумкин
30.07.2014 11:15

Выборки в следующем уроке, там примеры про ms2Gallery - очень похоже.

Сегодня допишу и опубликую.

kondakovДмитрий Кондаков
30.07.2014 11:18

Спасибо!

jean179Е. Вершинин
30.07.2014 13:55

Странно, в новую статью "Большой рассказ про pdoTools, часть вторая" не пускает - говорит не оплатил :) Кажется галку Василий забыл поставить где-то.

bezumkinВасилий Наумкин
30.07.2014 14:10

Василий её в первый курс опубликовал.

Поправил, заходи!

jean179Е. Вершинин
30.07.2014 16:04

Спасибо :)

Макс
30.07.2014 19:29

Василий! Предыдущие два поста понял почти все. Два поста про pdoTools не понял почти ничего :) Намекни пожалуйста мне по какой теме почитать литературу? HTML, CSS, PHP или все перечисленное? ну чтобы хоть приблизительно понять про что ты тут пишешь, а то вон народ одобрительно гудит, а я как-то совсем не в теме :)

bezumkinВасилий Наумкин
30.07.2014 19:46

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

А во-вторых, читать нужно про PHP и SQL.

Макс
30.07.2014 20:05

Про во-первых я тоже думал :) За во-вторых - спасибо! может и доберусь :)

Дмитрий Аверин
21.10.2014 11:14

Василий, вопрос новичка в Modx про pdoCrumbs, про крошки для вложенных документов (SEO!) Мы знаем, что нет ничего лучше для внутренней перелинковки и передачи веса, чем хлебные крошки.

Я бы хотел сделать так: Главная-Один-Два-Три site.ru/odin/dva/tri Для передачи веса на главную, которая будет продвигаться по определенным ключам.

Пока что у меня получается - site.ru/index/odin/dva/tri

Т.е. в крошки добавляется алис главной.

Как это исправить/настроить? Скорее всего я чего то не знаю пока. Спасибо

bezumkinВасилий Наумкин
21.10.2014 11:58

А зачем у тебя все документы внутри контейнера index?

Вытащи их оттуда, и не будет в пути /index/.

Дмитрий Аверин
21.10.2014 12:11

И получится структура site.ru/odin/dva/tri ? Я не знал о такой возможности. Думал, что надо создавать дочерние ресурсы, чтобы получить такую структуру. Т.к. я хочу, чтобы в крошках всегда присутствовала главная и передавать на ней вес.

bezumkinВасилий Наумкин
21.10.2014 12:13

У pdoCrumbs есть возможность выводить ссылку на главную, независимо от ссылки документа.

Обрати внимание на домик в крошках, вверху этой страницы.

Дмитрий Аверин
21.10.2014 12:21

Всё, врубился. Спасибо! Первые дни копаюсь с MODX после Joomla! Торможу еще.

Дмитрий Аверин
23.10.2014 12:33

У меня очень странно ЧПУ формируются в крошках. Вот пример теста- http://x0w.ru/odin/dva/tri/chetyire/pyat/shest Если кликнуть по крошке они все перемешиваются. Что не так сделал?

bezumkinВасилий Наумкин
24.10.2014 06:51

Ты слышал что-нибудь про тег base?

Добавь внутрь head

<base href="[[++site_url]]" />
futuris
Futuris
26.03.2024 07:39
Страница отдельного поста заработала сразу в том виде, как ты написал.) А вот в ленте постов контент...
bezumkin
Василий Наумкин
20.03.2024 18:21
Volledig!
Андрей
14.03.2024 10:47
Василий! Как всегда очень круто! Моё почтение!
russelgal
russel gal
09.03.2024 17:17
А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал ...
inetlover
Александр Наумов
27.01.2024 00:06
Василий, спасибо! Извини, тупанул.
bezumkin
Василий Наумкин
22.01.2024 04:43
Давай-давай!
bezumkin
Василий Наумкин
24.12.2023 11:26
Спасибо!
bezumkin
Василий Наумкин
27.11.2023 02:43
Ура!
bezumkin
Василий Наумкин
25.11.2023 08:30
Vesp тянет 2 зависимости: vesp-frontent для фронта и vesp-core для бэкенда. Их можно обновлять, но э...
bezumkin
Василий Наумкин
22.11.2023 08:09
Отлично, поздравляю!