23 авг. 2013 г., 14:31
$this->_lazy и повторные запросы к БД при $object->toArray() и ->get();
Наткнулся сегодня на странное поведение 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().
Не буду писать все, что хотел бы, но общая картина нарисована…