Обновление: Скорее всего, это ошибка, из-за которой возникла проблема с Jira: http://bit.ly/gpstW9
Обновление (5 мая 2011 г.): По рекомендации jwage я переключился на ссылочные отношения между категориями и публикацией (в отличие от Embdedded).
Я использую последнюю версию Doctrine ODM (только что из Git).
У меня есть три уровня документов (два встроенных); Категория -> EmbedsMany: Post -> EmbedsMany PostVersion.
PostVersion автоматически обрабатывается Post. Когда я делаю новое сообщение, оно фактически создает новое PostVersion под капотом.
Моя проблема в том, что Doctrine путают с PostVersions, если я извлекаю существующую категорию и добавляю в нее новую запись, новая запись PostVersions получает добавление к первой записи в коллекции $ posts категории.
Шаг за шагом:
- Создать новое сообщение (Post1) и категорию
- Добавить запись 1 в категорию
- Сохранять категорию, Флеш, Очистить
- Получить категорию
- Создать новое сообщение (Post2)
- Добавить запись 2 в категорию
- Flush
На этом этапе в базе данных должна быть одна категория, две публикации, и каждая запись имеет одну версию PostVersion. Однако в действительности существует одна категория, две публикации, первая запись имеет две версии PostVersions, а вторая запись имеет ноль PostVersions.
Сами документы во время запроса верны, просто нужно сохранить в базе данных, что неправильно. Чего мне не хватает?
Ожидаемый результат:
{
"_id": ObjectId("4da66baa6dd08df1f6000001"),
"name": "The Category",
"posts": [
{
"_id": ObjectId("4da66baa6dd08df1f6000002"),
"activeVersionIndex": 0,
"versions": [
{
"_id": ObjectId("4da66baa6dd08df1f6000003"),
"name": "One Post",
"content": "One Content",
"metaDescription": null,
"isAutosave": false,
"createdAt": "Thu, 14 Apr 2011 13:36:10 +1000",
"createdBy": "Cobby"
}
]
},
{
"_id": ObjectId("4da66baa6dd08df1f6000004"),
"activeVersionIndex": 0
"versions": [
{
"_id": ObjectId("4da66baa6dd08df1f6000005"),
"name": "Two Post",
"content": "Two Content",
"metaDescription": null,
"isAutosave": false,
"createdAt": "Thu, 14 Apr 2011 13:36:10 +1000",
"createdBy": "Cobby"
}
]
}
]
}
Фактический результат:
{
"_id": ObjectId("4da66baa6dd08df1f6000001"),
"name": "The Category",
"posts": [
{
"_id": ObjectId("4da66baa6dd08df1f6000002"),
"activeVersionIndex": 0,
"versions": [
{
"_id": ObjectId("4da66baa6dd08df1f6000003"),
"name": "One Post",
"content": "One Content",
"metaDescription": null,
"isAutosave": false,
"createdAt": "Thu, 14 Apr 2011 13:36:10 +1000",
"createdBy": "Cobby"
},
{
"_id": ObjectId("4da66baa6dd08df1f6000005"),
"name": "Two Post",
"content": "Two Content",
"metaDescription": null,
"isAutosave": false,
"createdAt": "Thu, 14 Apr 2011 13:36:10 +1000",
"createdBy": "Cobby"
}
]
},
{
"_id": ObjectId("4da66baa6dd08df1f6000004"),
"activeVersionIndex": 0
}
]
}
Вот мои документы
category.php
<?php
namespace Documents\Blog;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Document(collection="blog")
* @HasLifecycleCallbacks
*/
class Category
{
/**
* @Id
*/
private $id;
/**
* @String
*/
private $name;
/**
* @EmbedMany(targetDocument="Documents\Blog\Post")
*/
private $posts;
public function __construct($name = null)
{
$this->posts = new ArrayCollection();
$this->setName($name);
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getPosts()
{
return $this->posts->toArray();
}
public function addPost(Post $post)
{
$this->posts->add($post);
}
public function getPost($id)
{
return $this->posts->filter(function($post) use($id){
return $post->getId() === $id;
})->first();
}
}
Post.php
<?php
namespace Documents\Blog;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @EmbeddedDocument
* @HasLifecycleCallbacks
*/
class Post
{
/**
* @Id
*/
private $id;
private $firstVersion;
private $activeVersion;
/**
* @Int
*/
private $activeVersionIndex;
/**
* @EmbedMany(targetDocument="Documents\Blog\PostVersion")
*/
private $versions;
static private $currentUser;
private $isDirty = false;
public function __construct($name = "", $content = "")
{
if(!self::$currentUser){
throw new \BlogException("Cannot create a post without the current user being set");
}
$this->versions = new ArrayCollection();
$this->activeVersion = $this->firstVersion = new PostVersion($name, $content, self::$currentUser);
$this->versions->add($this->firstVersion);
$this->isDirty = true;
}
public function getId()
{
return $this->id;
}
public function getFirstVersion()
{
return $this->firstVersion;
}
public function getActiveVersion()
{
return $this->activeVersion;
}
public function setName($name)
{
$this->_setVersionValue('name', $name);
}
public function getName()
{
return $this->getActiveVersion()->getName();
}
public function setContent($content)
{
$this->_setVersionValue('content', $content);
}
public function getContent()
{
return $this->getActiveVersion()->getContent();
}
public function setMetaDescription($metaDescription)
{
$this->_setVersionValue('metaDescription', $metaDescription);
}
public function getMetaDescription()
{
return $this->getActiveVersion()->getMetaDescription();
}
public function getVersions()
{
return $this->versions->toArray();
}
private function _setVersionValue($property, $value)
{
$version = $this->activeVersion;
if(!$this->isDirty){
// not dirty, make a new version
$version = new PostVersion($version->getName(), $version->getContent(), self::getCurrentUser());
}
$refl = new \ReflectionProperty(get_class($version), $property);
$refl->setAccessible(true);
// updated current user
$refl->setValue($version, $value);
// unset ID
$refl = new \ReflectionProperty(get_class($version), 'id');
$refl->setAccessible(true);
$refl->setValue($version, null);
// updated self
if(!$this->isDirty){
$this->activeVersion = $version;
$this->versions->add($version);
$this->isDirty = true;
}
// no first version, this must be the first
if($this->versions->count() === 1){
$this->firstVersion = $version;
}
}
static public function setCurrentUser($user)
{
self::$currentUser = $user;
}
static public function getCurrentUser()
{
return self::$currentUser;
}
/**
* @PostLoad
*/
public function findFirstVersion()
{
$firstVersion = null;
foreach($this->versions as $version){
if(null === $firstVersion){
// first iteration, start with any version
$firstVersion = $version;
continue;
}
if($version->getCreatedAt() < $firstVersion->getCreatedAt()){
// current version is newer than existing version
$firstVersion = $version;
}
}
if(null === $firstVersion){
throw new \DomainException("No first version found.");
}
$this->firstVersion = $firstVersion;
}
/**
* @PostLoad
*/
public function findActiveVersion()
{
$this->activeVersion = $this->versions->get($this->activeVersionIndex);
}
/**
* @PrePersist
* @PreUpdate
*/
public function doActiveVersionIndex()
{
$this->activeVersionIndex = $this->versions->indexOf($this->activeVersion);
$this->isDirty = false;
}
/**
* @PostPersist
* @PostUpdate
*/
public function makeClean()
{
$this->isDirty = false;
}
public function getCreatedBy()
{
return $this->getFirstVersion()->getCreatedBy();
}
public function getCreatedAt()
{
return $this->getFirstVersion()->getCreatedAt();
}
}
PostVersion.php
<?php
namespace Documents\Blog;
/**
* @EmbeddedDocument
*/
class PostVersion
{
/**
* @Id
*/
private $id;
/**
* @String
*/
private $name;
/**
* @String
*/
private $content;
/**
* @String(nullable="true")
*/
private $metaDescription;
/**
* @Boolean
*/
private $isAutosave = false;
/**
* @Date
*/
private $createdAt;
/**
* @String
*/
private $createdBy;
public function __construct($name, $content, $author)
{
$this->setName($name);
$this->setContent($content);
$this->setCreatedBy($author);
$this->touch();
}
public function __clone()
{
if($this->id){
$this->id = null;
$this->touch();
}
}
private function touch()
{
$this->createdAt = new \DateTime();
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getContent()
{
return $this->content;
}
public function setContent($content)
{
$this->content = $content;
}
public function getIsAutosave()
{
return $this->isAutosave;
}
public function setIsAutosave($isAutosave)
{
$this->isAutosave = $isAutosave;
}
public function getCreatedAt()
{
return $this->createdAt;
}
public function setCreatedAt(\DateTime $createdAt)
{
$this->createdAt = $createdAt;
}
public function getCreatedBy()
{
return $this->createdBy;
}
public function setCreatedBy($createdBy)
{
$this->createdBy = $createdBy;
}
public function setMetaDescription($metaDescription)
{
$this->metaDescription = $metaDescription;
}
public function getMetaDescription()
{
return $this->metaDescription;
}
}
... думаю, пришло время испачкаться в xdebug.