如何解决发出持久嵌套嵌套的嵌入式文档
| 更新:可以肯定,这是一个错误,在Jira上引起了问题:http://bit.ly/gpstW9 更新(2011年5月5日):根据jwage的建议,我已切换到“类别”和“帖子”之间的“引用关系”(与“嵌入”相对)。 我正在使用最新版本的Doctrine ODM(来自Git的最新版本)。 我有三个级别的文档(两个嵌入式);类别-> EmbedsMany:发布-> EmbedsMany PostVersion。 PostVersion由Post自动处理。当我发表新文章时,实际上也会在幕后制作新的PostVersion。 我的问题是,Doctrine与PostVersions混淆,如果我检索现有的Category并向其中添加新的Post,则新Post \的PostVersions会添加到Category的$ posts集合中的第一个Post中。 一步步: 新建一个帖子(Post1)和类别 将Post1添加到类别 持久性类别,冲洗,清除 检索类别 撰写新文章(Post2) 将Post2添加到类别 冲洗 在数据库的这一阶段,应该有一个类别,两个帖子,每个帖子都有一个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\",\"createdBy\": \"Cobby\"
}
]
}
]
}
实际结果:
{
\"_id\": ObjectId(\"4da66baa6dd08df1f6000001\"),\"createdBy\": \"Cobby\"
},{
\"_id\": ObjectId(\"4da66baa6dd08df1f6000005\"),\"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,$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了。
解决方法
现在,我已经通过创建一个EventSubscriber来解决此问题,该事件会延迟持久化嵌套的嵌入式文档,如下所示:
<?php
namespace Application\\Blog\\Domain\\EventSubscribers;
use Application\\Blog\\Domain\\Document\\Post,Doctrine\\ODM\\MongoDB\\Event\\LifecycleEventArgs,Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata;
/**
* Handles delayed insert of nested embedded documents to work around Doctrine ODM bug :(
*/
class VersionManager implements \\Doctrine\\Common\\EventSubscriber
{
private $versions = array();
/**
* Returns an array of events this subscriber wants to listen to.
*
* @return array
*/
public function getSubscribedEvents()
{
return array(\'prePersist\',\'postPersist\');
}
/**
* Move versions out of Posts into temporary storage so they are flushed without versions
*
* @param \\Doctrine\\ODM\\MongoDB\\Event\\LifecycleEventArgs $eventArgs
* @return void
*/
public function prePersist(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if($document instanceof Post){
$dm = $eventArgs->getDocumentManager();
$meta = $dm->getClassMetadata(get_class($document));
$this->addVersion($meta,$document);
$this->clearVersions($meta,$document);
}
}
/**
* Move the temporary versions back onto the Posts and flush
*
* @param \\Doctrine\\ODM\\MongoDB\\Event\\LifecycleEventArgs $eventArgs
* @return void
*/
public function postPersist(LifecycleEventArgs $eventArgs)
{
$dm = $eventArgs->getDocumentManager();
$hasChanges = count($this->versions) > 0;
foreach($this->versions as $oid => $value){
$post = $value[\'document\'];
$versions = $value[\'versions\'];
$meta = $dm->getClassMetadata(get_class($post));
$meta->setFieldValue($post,\'versions\',$versions);
unset($this->versions[$oid]);
}
if($hasChanges){
$dm->flush();
}
}
/**
* Add versions to temporary storage
*
* @param \\Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata $meta
* @param \\Application\\Blog\\Domain\\Document\\Post $post
* @return void
*/
private function addVersion(ClassMetadata $meta,Post $post)
{
$this->versions[spl_object_hash($post)] = array(
\'document\' => $post,\'versions\' => $meta->getFieldValue($post,\'versions\')
);
}
/**
* Remove versions from a Post
*
* @param \\Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata $meta
* @param \\Application\\Blog\\Domain\\Document\\Post $post
* @return void
*/
private function clearVersions(ClassMetadata $meta,Post $post)
{
$meta->setFieldValue($post,null);
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。