Fi1osof 23 августа 2013 1 0
Наткнулся сегодня на странное поведение xPDO при попытке извлечения записи объекта из базы данных не со всеми колонками. Вот запрос для примера
$q = $modx->newQuery('modResource');
$q->select(array(
    'modResource.id',  
)); 
$q->where(array(
    'id' => 2    
));
if($o = $modx->getObject('modResource', $q)){
    print_r($o->toArray());    
}


И вот что интересно: здесь мы формируем запрос так, что из базы данных мы извлекаем только одну колонку — id. То есть наш конечный объект должен содержать только значение id, а все остальные поля должны были содержать дефолтовые значения из мета-описания объекта. Но при этом, когда мы выводим данные объекта через $o->toArray(), то видим все значения из базы данных. В чем подвох? Может запрос полный со всеми колонками, а не только с id? Посмотрим.
print $q->toSQL();
SELECT modResource.id FROM `modx_site_content` AS `modResource` WHERE `modResource`.`id` = 2

Нет, в запросе только одна колонка id…

А прикол весь в том, что xPDO четко следит за тем, все ли «колонки» объекта заполнены (получены из базы данных). Но это довольно сложный и запутанный механизм.

Итак, у xPDO-объектов есть свойство $this->_lazy, которое содержит массив всех колонок, которые не были получены из базы данных. Изначально этот массив пустой, и наполняется только в методе xPDOObject::_loadInstance, и только тогда, когда не все колонки объекта запрошены из базы данных. Давайте внимательней взглянем на эту строку
$instance->_lazy= $actualClass !== $className ? array_keys($xpdo->getFieldMeta($actualClass)) : array_keys($instance->_fieldMeta);

То есть в этот массив попадают или все колонки актуального класса (этот механизм я расписывал здесь), или массив колонок из мета-описания этого объекта, не содержащихся в массиве запрошенных колонок. В нашем случае в этот массив попали все колонки, кроме id.

А что происходит дальше и на все влияет этот массив ->lazy? Для этого стоит посмотреть методы xPDOObject::get(), xPDOObject::toArray() и xPDOObject::toArray().

В методе xPDOObject::get() есть строки:
$lazy = array_intersect($k, $this->_lazy);
if ($lazy) {
    $this->_loadFieldData($lazy);
}

То есть если это поле — lazy (дословно «ленивое»), то он пытается подгрузить значение через метод $this->_loadFieldData.
А что там?
protected function _loadFieldData($fields) {
    if (!is_array($fields)) $fields= array($fields);
    else $fields= array_values($fields);
    $criteria= $this->xpdo->newQuery($this->_class, $this->getPrimaryKey());
    $criteria->select($fields);
    if ($rows= xPDOObject :: _loadRows($this->xpdo, $this->_class, $criteria)) {
        $row= $rows->fetch(PDO::FETCH_ASSOC);
        $rows->closeCursor();
        $this->fromArray($row, '', false, true);
        $this->_lazy= array_diff($this->_lazy, $fields);
    }
}

А там, как видно, формируется новый запрос к БД.

Примерно тоже самое видно и в методе xPDOObject::toArray()
if (!$excludeLazy && $this->isLazy()) {
    $this->_loadFieldData($this->_lazy);
}


То есть, если вы решили сократить запрос к БД, указав меньшее количество колонок в запросе (в xPDOCriteria), то на первых порах вы получите небольшую экономию, но в дальнейшем можно автоматом наплодить новые запросы к БД при попытке получить значения объекта.

Но в чем еще казусы? В методе xPDOObject::fromArray происходит перезапись ->_lazy (полученные из массива колонки отмечаются как «не ленивые»)
if ($this->isLazy($key)) {
    $this->_lazy = array_diff($this->_lazy, array($key));
}

А вот в методе xPDOObject::set (который по сути делает тоже самое, что и :fromArray(), только для одного поля) — нифига. К чему это приводит?
Вот такая конструкция:
$q = $modx->newQuery('modResource');
$q->select(array(
    'modResource.id',  
)); 
$q->where(array(
    'id' => 2    
));
if($o = $modx->getObject('modResource', $q)){
    $o->set('pagetitle', 'new  pagetitle');
    print "<br />Pagetitle 1: ". $o->pagetitle;
    print "<br />Pagetitle 2: ". $o->get('pagetitle');   
}
То есть мы получили объект практически без данных. Затем установили ему pagetitle 'new pagetitle'.
Вот если после этого мы получаем pagetitle как свойство объекта, то мы получим установленное значение.
А вот если попытаемся получить через метод ->get('pagetitle');, то так как поле pagetitle _lazy, то он получит данные из БД и перетрет это значение.
Так же это значение будет затерто, если мы выполним ->toArray().

Не буду писать все, что хотел бы, но общая картина нарисована…
0 комментариев
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.