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