7

更新:これは間違いなくバグで、Jira で問題が発生しました: http://bit.ly/gpstW9
更新 (2011 年 5 月 5 日): jwage の推奨により、カテゴリと投稿の間の参照関係 (埋め込みではなく) に切り替えました。

Doctrine ODM の最新バージョン (Git から新しく) を使用しています。

3 つのレベルのドキュメント (2 つの埋め込み) があります。カテゴリ -> EmbedsMany: 投稿 -> EmbedsMany PostVersion。

PostVersion は、Post によって自動的に処理されます。新しい投稿を作成すると、実際には内部で新しい PostVersion も作成されます。

私の問題は、Doctrine が PostVersions と混同されることです。既存のカテゴリを取得してそれに新しい投稿を追加すると、新しい投稿の PostVersions がカテゴリの $posts コレクションの最初の投稿に追加されます。

ステップバイステップ:

  1. 新しい投稿 (Post1) とカテゴリを作成します
  2. Post1 をカテゴリに追加
  3. パーシスト カテゴリ、フラッシュ、クリア
  4. カテゴリを取得
  5. 新しい投稿を作成する (Post2)
  6. Post2 をカテゴリに追加
  7. 流す

データベースのこの段階では、1 つのカテゴリと 2 つの投稿があり、各投稿には 1 つの PostVersion があります。ただし、実際には、カテゴリが 1 つ、投稿が 2 つあり、最初の投稿には 2 つの PostVersion があり、2 番目の投稿には PostVersion がありません。

リクエスト中のドキュメント自体は正しいですが、間違っているデータベースに永続化したいだけです。私は何が欠けていますか?

期待される結果:

{
  "_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
    }
  ]
}

これが私のドキュメントです

カテゴリ.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 で汚れる時が来たと思います。

4

1 に答える 1

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);
    }

}
于 2011-04-19T01:17:14.193 に答える