发出持久嵌套嵌套嵌入文档
更新: 非常确定这是一个错误,在 Jira 上提出了一个问题:http://bit.ly/ GPSW9
更新(2011 年 5 月 5 日):根据 jwage 的建议,我已切换到类别和帖子之间的引用关系(而不是嵌入)。
我正在使用最新版本的 Doctrine ODM(来自 Git)。
我有三个级别的文档(两个嵌入式);类别-> EmbedsMany:发布 ->嵌入许多帖子版本。
PostVersion 由 Post 自动处理。当我发布新帖子时,它实际上也在幕后创建了一个新的帖子版本。
我的问题是 Doctrine 与 PostVersions 混淆,如果我检索现有类别并向其添加新帖子,新帖子的 PostVersions 将添加到类别 $posts 集合中的第一个帖子。
一步一步:
- 制作新帖子 (Post1) 和类别
- 将 Post1 添加到类别
- 保留类别、刷新、清除
- 检索类别
- 制作新帖子 (Post2)
- 将 Post2 添加到类别
- 刷新
在此阶段,数据库中应该有一个类别,两个帖子,每个帖子有一个帖子版本。然而,实际发生的情况是有一个类别,两个帖子,第一个帖子有两个帖子版本,第二个帖子有零个帖子版本。
请求期间的文档本身是正确的,只是希望将其保存到错误的数据库中。我缺少什么?
预期结果:
{
"_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 了。
Update: Pretty certain this a bug, made an issue on Jira: http://bit.ly/gpstW9
Update (5th May 2011): Upon jwage's recommendation I have switch to Referenced relationships between Categories and Post (as opposed to Embdedded).
I'm using the latest version of Doctrine ODM (fresh from Git).
I have three levels of Documents (Two embedded); Category -> EmbedsMany: Post -> EmbedsMany PostVersion.
PostVersion is automatically handled by Post. When I make a new post, it actually makes a new PostVersion under the hood as well.
My issue is that Doctrine get confused with PostVersions, if I retrieve an existing Category and add a new Post to it, the new Post's PostVersions get add to the first Post in the Category's $posts collection.
Step-by-step:
- Make a new Post (Post1) and Category
- Add Post1 to Category
- Persist Category, Flush, Clear
- Retrieve Category
- Make a new Post (Post2)
- Add Post2 to Category
- Flush
At this stage in the database, there should be one Category, two Posts and each Post has one PostVersion. However, what actually happens is there is one Category, two Posts, the first Post has two PostVersions and the second Post has zero PostVersions.
The Documents themselves during the request are correct, it's just want is persisted to the database that is wrong. What am I missing?
Expected Result:
{
"_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"
}
]
}
]
}
Actual Result:
{
"_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
}
]
}
Here are my Documents
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;
}
}
...time to get dirty with xdebug I think.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
现在我已经通过创建一个 EventSubscriber 来解决这个问题,它会延迟持久的嵌套嵌入文档,它看起来像这样:
For now I've worked around the issue by creating an EventSubscriber which delays persisted nested embedded documents, it looks like this: