"Сериализация" объекта XPDOQuery
Как оказалось, у меня только лишь подготовка объекта XPDOQuery (создание, подключение TV, условия) занимает 0.1 сек. И это без выполнения запроса.
TV и условий там много. Именно поэтому, и набегает столько времени. Логично здесь реализовать кэширование этого объекта. Но проблема в том, что этот объект НЕсериализуем.
.
Кэширование результата выполнения запроса выполняет MySQL (да и самому можно сделать кэширование результирующего массива данных без запроса к БД). Но проблема в том, что запросы каждый раз разные и отличаются совсем чуть-чуть (например, с разными LIMIT и OFFSET при пагинации). Т.е. каждый раз при загрузке нового page'а уходит 0.1 сек на подготовку xpdo (а именно — подключение ряда TV и условий — тех же самых).
Как можно запомнить состояние объекта XPDOQuery для его последующего быстрого восстановления?
TV и условий там много. Именно поэтому, и набегает столько времени. Логично здесь реализовать кэширование этого объекта. Но проблема в том, что этот объект НЕсериализуем.
.
Кэширование результата выполнения запроса выполняет MySQL (да и самому можно сделать кэширование результирующего массива данных без запроса к БД). Но проблема в том, что запросы каждый раз разные и отличаются совсем чуть-чуть (например, с разными LIMIT и OFFSET при пагинации). Т.е. каждый раз при загрузке нового page'а уходит 0.1 сек на подготовку xpdo (а именно — подключение ряда TV и условий — тех же самых).
Как можно запомнить состояние объекта XPDOQuery для его последующего быстрого восстановления?
Комментарии: 2
Имеем такую цепочку наследования:
xPDOQuery_mysql extends xPDOQuery extends xPDOCriteria
Самый простой вариант — засериализовать все поля объекта xPDOQuery_mySQL. Проблема в том, что открытыми являются только поля класса xPDOCriteria:
Соответственно, единственным способом вручную засериализовать поля объекта xPDOQuery (xPDOQuery_mysql) остаётся наследование. Что-то вроде такого:
В голову лезет ещё вариант с reflection. Может, к утру что-нибудь придумаю…
xPDOQuery_mysql extends xPDOQuery extends xPDOCriteria
Самый простой вариант — засериализовать все поля объекта xPDOQuery_mySQL. Проблема в том, что открытыми являются только поля класса xPDOCriteria:
class xPDOCriteria {
public $sql= '';
public $stmt= null;
public $bindings= array ();
public $cacheFlag= false;
...
Собственные же поля класса xPDOQuery являются защищёнными.Соответственно, единственным способом вручную засериализовать поля объекта xPDOQuery (xPDOQuery_mysql) остаётся наследование. Что-то вроде такого:
class xPDOQueryManager_mysql extends xPDOQuery_mysql {
public function __construct(& $xpdo, $class, $criteria= null) {
parent::__construct($xpdo, $class, $criteria);
}
// реализация паттерна memento
public function save() { ... }
public function restore() { ... }
}
Но в этом случае также потребуется заставить метод xpdo->newQuery вместо xPDOQuery_mysql создавать объект класса xPDOQueryManager_mysql. Опять-таки заставить можно, только перегрузив класс modx и метод newQuery. И это не всё. Нужно ещё заставить modx в процессе инициализации вместо modx использовать перегруженный класс modxManager. Но это уже попахивает извращением.В голову лезет ещё вариант с reflection. Может, к утру что-нибудь придумаю…
Поскольку reflection позволяет извне получить доступ к защищённым/закрытым полям объекта, то через reflection сабжевая задача решается гораздо проще:
Далее в коде делаем так (кэшируем состояние объекта xPDOQuery):
Тест (1 TV и 2 простых условия):
При первом выполнении (запрос строится с нуля): 0.005-0.007 сек
При последующих выполнениях (объект xPDOQuery восстанавливается из файлового кэша): 0.0007 сек (из них 0.0005 сек — чтение кэш-файла с состоянием xPDOQuery)
Разница: в 7-10 раз. И это только на простом запросе (1 tv и 2 условия).
Мой запрос с 30 tv-ками и кучей сложных условий готовится (без выполнения) 0.1 сек.
С кэшированием запроса вплоть до пагинации (до добавления limit/offset) — 0.0007-0.0010 сек
Разница: в 70-100 раз.
P.S. Кэширование запроса xPDOQuery совместно с его выполнением в режиме pdo может придать ему реактивные свойства. И переписывать запрос на чистый pdo не потребуется.
abstract class xPDOQueryMemento {
static $reflectionProperties;
static protected function getReflectionProperties() {
global $modx;
if(!isset(xPDOQueryMemento::$reflectionProperties)) {
$reflection = new ReflectionClass('xPDOQuery_'.$modx->config['dbtype']);
$properties = array();
foreach($reflection->getProperties() as $property) {
$properties[$property->getName()] = $property;
}
xPDOQueryMemento::$reflectionProperties = $properties;
}
return xPDOQueryMemento::$reflectionProperties;
}
// memento pattern save
static public function save($query) {
global $modx;
if(is_null($query)) { return ''; }
$data = array();
$properties = xPDOQueryMemento::getReflectionProperties(); // $reflection = new ReflectionObject($query);
foreach($properties as $property) {
$property->setAccessible(true);
$value = $property->getValue($query);
if(!is_object($value) || is_null($value)) {
$data[$property->getName()] = $value;
}
if (!$property->isPublic()) { $property->setAccessible(false); }
}
return serialize($data);
}
// memento pattern restore
static public function restore($data) {
global $modx;
if(($data == '') || is_null($data)) { return null; }
$query = $modx->newQuery('modResource');
$properties = xPDOQueryMemento::getReflectionProperties(); // $reflection = new ReflectionObject($query);
foreach(unserialize($data) as $name => $value) {
$property = $properties[$name];
$property->setAccessible(true);
$property->setValue($query, $value);
if (!$property->isPublic()) { $property->setAccessible(false); }
}
return $query;
}
}
Далее в коде делаем так (кэшируем состояние объекта xPDOQuery):
$key = md5('Ключ кэша с состоянием объекта xPDOQuery');
$query = xPDOQueryMemento::restore($modx->cacheManager->get($key));
if(is_null($query)) {
$query = $modx->newQuery('modResource');
... // Сложные операции с query (подключение TV, условия и пр.)
cacheManager::cacheSet($key, xPDOQueryMemento::save($query));
}
... // Часто меняющиеся операции с query. Например, указание LIMIT/OFFSET при пагинации
Тест (1 TV и 2 простых условия):
$time0 = microtime(true);
$key = md5('key125');
$query = xPDOQueryMemento::restore($modx->cacheManager->get($key));
if(is_null($query)) {
$query = $modx->newQuery('modResource');
$query->leftJoin('modTemplateVarResource', 'tv', array('tv.contentid = modResource.id', 'tv.tmplvarid = 1239');
$query->where(array('modResource.id:=' => 1000, 'tv.value:>' => 25), xPDOQuery::SQL_OR);
cacheManager::cacheSet($key, xPDOQueryMemento::save($query));
}
print_r(number_format(microtime(true) - $time0, 4).' сек');
При первом выполнении (запрос строится с нуля): 0.005-0.007 сек
При последующих выполнениях (объект xPDOQuery восстанавливается из файлового кэша): 0.0007 сек (из них 0.0005 сек — чтение кэш-файла с состоянием xPDOQuery)
Разница: в 7-10 раз. И это только на простом запросе (1 tv и 2 условия).
Мой запрос с 30 tv-ками и кучей сложных условий готовится (без выполнения) 0.1 сек.
С кэшированием запроса вплоть до пагинации (до добавления limit/offset) — 0.0007-0.0010 сек
Разница: в 70-100 раз.
P.S. Кэширование запроса xPDOQuery совместно с его выполнением в режиме pdo может придать ему реактивные свойства. И переписывать запрос на чистый pdo не потребуется.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.