Не буду сейчас расписывать про весь механизм сохранения связанных объектов, а приведу пару примеров и расскажу про тонкость, ради которой и пишется этот топик.
Возьмем два новых объекта (пользователя и его профиль), добавим профиль в пользователя, и сохраним пользователя. Результат: будет сохранен и профиль, плюс еще и в качестве internalKey будет присвоен id нового пользователя. Вот код:
$user = $modx->newObject('modUser', (array)$userdata); $profile = $modx->newObject('modUserProfile', (array)$profiledata); $user->addOne($profile); $user->save();
Так же можно работать и с имеющимися объектами, к примеру получим профиль пользователя, изменим его, и сохраним объект самого профиля. Результат: профиль будет сохранен.
$user = $modx->getObject('modUser', $id); $profile = $user->getOne('Profile'); $profile->fromArray((array)$profiledata); $user->save();
Собственно, здесь тоже все замечательно. xPDO сам отследит, что связанный с пользователем объект профиля был изменен, и сохранит изменения профиля в базу. Но как будут обстоять дела, если будет изменен профиль во втором уровне вложенности?
Для примера возьмем профиль пользователя, создавшего документ, изменим его, и сохраним документ.
$doc = $modx->getObject('modResource', $id); $user = $doc->getOne('CreatedBy'); $profile = $user->getOne('Profile'); $profile->set('fullname', 'New name'); $doc->save();
И вот здесь как раз и есть загвоздка. Дело в том, что xPDO сохраняет объект только тогда, когда у него есть хоть одна dirty-колонка (измененная). То есть он вызывает метод xPDOObject::save(), в котором вызывается метод _saveRelatedObjects(), сохраняющий связанные объекты. А так как у нас объект пользователя не был изменен, то xPDO его не сохраняет (не вызывает метод save()), а значит и не сохраняет связанные с ним объекты. Следовательно и профиль пользователя не сохраняется.
Давайте еще раз рассмотрим последовательность действий:
- Мы сохраняем документ — вызываем метод $doc->save();
- В этом методе вызывается метод _saveRelatedObjects(), сохраняя связанные объекты. В нашем случае связанный объект ближайшего уровня — $user.
- Далее, каждый связанный объект проверяется на наличие измененных колонок ( if (!empty ($this->_dirty)) ), и если таковые имеются, то этот объект сохраняется (и смотрим опять все начиная с первого пункта, только уже для этого объекта).
Конечно, я считаю, что данный механизм неплохо было бы доработать, чтобы все вложенные объекты проверялись, но пока мы просто рассмотрим вариант решения этой проблемы (так как не знаю кому как, а мне вот понадобилось реализовать сохранение объекта третьего уровня только через сохранение объекта первого уровня. Дело в том, что хочется, чтобы объект сохранялся только в том случае, если сохранятся все предыдущие объекты, а проверки лишние писать не охота).
В общем, как оказалось, здесь только или через явное сохранение предшествующего объекта (почему мне такой момент не подходит, написал выше), или через маркировку предшествующего объекта, будто бы он изменен. В нашем случае код будет выглядеть так:
$doc = $modx->getObject('modResource', $id); $user = $doc->getOne('CreatedBy'); $profile = $user->getOne('Profile'); $profile->set('fullname', 'New name'); if($profile->_dirty){ $user->setDirty('id'); } $doc->save();
То есть проверяем, если колонки есть измененные в профиле, то помечаем колонку id юзера как измененную. В итоге, при сохранении документа, xPDO попытается и пользователя сохранить, и затем его профиль. Конечно это хак, но так, на заметку…