2011-04-14 69 views
7

更新:http://bit.ly/gpstW9
更新(2011年5月5日):相當肯定這是一個錯誤,在吉拉做出一個問題
在jwage的建議我已經切換到分類和郵政之間引用關係(而不是Embdedded)。問題持續嵌套嵌套嵌入文檔

我正在使用最新版本的Doctrine ODM(來自Git的新鮮版本)。

我有三個級別的文件(兩個嵌入); Category - > EmbedsMany:Post - > EmbedsMany PostVersion。

PostVersion自動由Post處理。當我發佈一篇新文章時,它實際上也在引擎蓋下創建了一個新的PostVersion。

我的問題是,學說與PostVersions混淆,如果我檢索一個現有的類別並添加一個新的帖子,新的帖子的PostVersions獲取添加到類別的$ posts集合中的第一篇文章。

步驟一步:

  1. 建立新帖子(POST1)和類別
  2. 添加POST1類別
  3. 堅持範疇,沖洗,清除
  4. 檢索類別
  5. 製作一個新帖子(Post2)
  6. 將Post2添加到類別
  7. Flush

在這個數據庫階段,應該有一個Category,兩個Posts,每個Post都有一個PostVersion。但是,實際發生的情況是有一個類別,兩個帖子,第一個Post有兩個PostVersions,第二個Post有零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弄髒的時間我想。

+0

+1有趣的問題:) – alex 2011-04-14 04:00:42

回答

1

現在我已經圍繞這一問題通過創建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, 'versions', null); 
    } 

} 
+0

是這樣曾經在教義本身作爲解決我也有類似的東西遇到麻煩。 – 2012-05-31 06:44:02

+0

我看到你發現GitHub上的問題,它仍然是開放的:https:github.com/doctrine/mongodb-odm/pull/232 – Cobby 2012-06-01 03:51:56

+0

是的,謝謝。公關爲我工作。希望他們很快就會合並。 – 2012-06-01 04:55:42