"Сериализация" объекта XPDOQuery

Как оказалось, у меня только лишь подготовка объекта XPDOQuery (создание, подключение TV, условия) занимает 0.1 сек. И это без выполнения запроса.
TV и условий там много. Именно поэтому, и набегает столько времени. Логично здесь реализовать кэширование этого объекта. Но проблема в том, что этот объект НЕсериализуем.
.

Кэширование результата выполнения запроса выполняет MySQL (да и самому можно сделать кэширование результирующего массива данных без запроса к БД). Но проблема в том, что запросы каждый раз разные и отличаются совсем чуть-чуть (например, с разными LIMIT и OFFSET при пагинации). Т.е. каждый раз при загрузке нового page'а уходит 0.1 сек на подготовку xpdo (а именно — подключение ряда TV и условий — тех же самых).

Как можно запомнить состояние объекта XPDOQuery для его последующего быстрого восстановления?
Cyrax_02
25 сентября 2014, 08:44
modx.pro
2
1 398
0

Комментарии: 2

Cyrax_02
28 сентября 2014, 02:50
0
Имеем такую цепочку наследования:
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. Может, к утру что-нибудь придумаю…
    Cyrax_02
    30 сентября 2014, 14:43
    1
    +1
    Поскольку reflection позволяет извне получить доступ к защищённым/закрытым полям объекта, то через reflection сабжевая задача решается гораздо проще:

    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 не потребуется.
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      2