Заполнение свойств из Embedded другого документа
// Декабрь 18th, 2012 // Doctrine 2
На днях столкнулись с коллегой с одним занимательным багом. По какой-то причине не заполняся Embedded документ в Doctrine ODM. При всём при этом, когда мы его дампили, то он исправно показывался, а вот до самой MongoDB так и не доходил.
Проблема
Суть баги можно выразить вот таким псевдокодом.
/* @var Doctrine\ODM\MongoDB\DocumentManager */
$a = $dm->getRepository('Document\SomeDoc')->findOneBy(array('id' = > '50a5468f0d42e2344800003c');
$b = $dm->getRepository('Document\AnotherDoc')->findOneBy(array('id' = > '50a5468f0d42e2344800003c');
$b->setSomeEmbeddedOneField($a->getSomeEmbeddedOneField());
После этого при дампе объекта $b в нём в свойстве someEmbeddedOneField лежит наш Embedded документ, а вот при сохранении — ничего не сохраняется.
$dm->persist($b); $dm->flush();
Причина проблемы
После долгих ночных изысканий выяснилось, что проблема кроется в том, что доктрина при сохранении документов проходится по их дереву, от родителя к потомкам (Embedded). Когда она добирается до нашего Embedded документа, то получает ссылку на Embedded из объекта a. А ведь он то в базе сохранён и пересохранять его не надо. Поэтому нет запроса. В PHP объекты передаются по ссылке, вот и получается, что у нас в объекте b в поле someEmbeddedOneField стоит ссылка на Embedeed объекта а.
Возможные решения
Костыль
$this->dm->getUnitOfWork()->clear('Document\SomeEmbeddedDocument');
Решение получше
$с = new Document\SomeEmbeddedDocument(); $c->fromArray($a->getSomeEmbeddedOneField->toArray(); $b->setSomeEmbeddedOneField($ c);
Решение ещё лучше
Просто клонируем объект, полученный из другого документа.
/* @var Doctrine\ODM\MongoDB\DocumentManager */
$a = $dm->getRepository('Document\SomeDoc')->findOneBy(array('id' = > '50a5468f0d42e2344800003c');
$b = $dm->getRepository('Document\AnotherDoc')->findOneBy(array('id' = > '50a5468f0d42e2344800003c');
$b->setSomeEmbeddedOneField(clone $a->getSomeEmbeddedOneField());
А теперь, правильное решение
У предыдушего варианта есть один недостаток. Он клонирует все объекты не разбирая, новые они (клонирование в этом случчае не нужно) или уже есть в БД (клонирование нужно). Поэтому надо узнавать, а сохранён ли объект в БД, т.е. является ли он managed в терминологии Doctrine. Это можно сделать так:
/**
* Проверяет, есть ли в БД уже данный экземпляр Embedded Document
* Если есть, то клонирует его. Это предотвращает проблему с записями embedded в документ, при том,
* что этот embedded только что взят из другого документа, и есть в БД и не менялся.
* @static
* @param $document
*/
public static function getEmbeddedForSetter($document)
{
if(self::$dm->contains($document))
return clone $document;
return $document;
}
А в мутатор добавить вызов этой функции.
public function setSomeEmbeddedField($document)
{
$this->someEmbeddedField = getEmbeddedForSetter($document);
}
Да, функцию getEmbeddedForSetter лучше убрать в ваш сервисный слой.
Ссылки
EntityState (это про ORM на самом деле, но прочитать стоит)
Смотрите также:
- Rjvfyls rails rails new
- Netbeans язык интерфейса
- Печать брошюры в word
- Репликация mysql master master
Спасибо!
Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:









Спасибо огромное, очень помогли. Долго не мог понять, почему не сохраняются Embedded документы. Теперь все стало намного понятнее.
Рад помочь, сам с этими граблями долго просидел)