Наткнулся сегодня на странное поведение 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(). Не буду писать все, что хотел бы, но общая картина нарисована…