pdoTools

16 июня 2016, 15:52

Основной класс компонента, который наследуют все остальные (кроме 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 и писать в чанках более продвинутую логику. Про работу с Fenom читайте в разделе pdoParser.