В этой заметки я хочу наконец-то подробно рассказать, что же на самом деле умеет мой, пожалуй, самый главный компонент для MODX -
pdoTools.
Изначально он не задумывался, как набор универсальных сниппетов, нет. Он должен был стать набором классов, на основе которых программисты
могли бы разрабатывать собственные сниппеты. Однако, идея не прижилась, и сниппеты на нём разрабатывал один я.
Понятное дело, что через какое-то время я пришел к универсальным сниппетам "на все случаи жизни", которые и вошли в комплект pdoTools. Про них вы можете
почитать на страницах документации, а ниже я расскажу, что же там под капотом.
Вы узнаете, как pdoTools работает с чанками, что такое быстрые плейсхолдеры, как делать выборки из сторонних таблиц, присоединять их в запросы и т.д. В общем, масса полезной информации.
При установке в систему они регистрируются таким образом, чтобы вы могли быстро их запускать:
$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.
Чем же можно заменить условия? Самые простые "пусто\не пусто" заменяются "быстрыми плейсхолдерами".
Работает это так:
-
В чанке должен быть какой-то тег, например [[+tag]].
-
В чанке должен быть специальный 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 и как с его помощью выбирать что угодно на сайте.