2011-04-14 9 views
7

Actualización: bastante seguro de esto un error, hizo un tema en Jira: http://bit.ly/gpstW9
actualización (5 de mayo de 2011): Por recomendación de jwage tengo interruptor para relaciones referenciadas entre las categorías y Correos (a diferencia de Embdedded).persistente problema inclusión de documentos anidados anidados

Estoy usando la última versión de Doctrine ODM (recién salido de Git).

Tengo tres niveles de documentos (dos incrustados); Categoría -> EmbrujadasMany: Publicaciones -> EmbárcadasMany PostVersión.

PostVersion es automáticamente manejado por Post. Cuando hago una publicación nueva, también hace una nueva versión de PostVersion bajo el capó.

Mi problema es que Doctrine se confunde con PostVersions, si recupero una categoría existente y agrego una nueva publicación a ella, la PostVersions del nuevo post se agrega al primer mensaje en la colección de $ publicaciones de la categoría.

Paso a paso:

  1. crear un nuevo mensaje (Post1) y Categoría
  2. Añadir Post1 a Categoría
  3. Persistir Categoría, Flush, Claro
  4. Recuperar Categoría
  5. Hacer un nuevo mensaje (Post2)
  6. Add Post2 to Category
  7. Flush

En esta etapa de la base de datos, debe haber una Categoría, dos Publicaciones y cada Publicación tiene una Versión de Publicación. Sin embargo, lo que realmente sucede es que hay una Categoría, dos Publicaciones, la primera Publicación tiene dos PostVersiones y la segunda Publicación tiene cero PostVersiones.

Los documentos en sí mismos durante la solicitud son correctos, lo único que desea es persistir en la base de datos que está mal. ¿Qué me estoy perdiendo?

Resultado esperado:

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

Resultado real:

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

Éstos son mis documentos

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

} 

... tiempo para ensucian con Xdebug creo.

+0

+1 tema interesante :) – alex

Respuesta

1

Por ahora he trabajado en torno al tema mediante la creación de un EventSubscriber que retrasa persistieron documentos incrustados anidados, que se parece a esto:

<?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

fue esta vez resueltos en sí mismo como Doctrina Yo también estoy teniendo problemas con algo similar. –

+0

Veo que encontró el problema en GitHub, todavía está abierto: https://github.com/doctrine/mongodb-odm/pull/232 – Cobby

+0

Sí, gracias. Ese RP me funcionó. Con suerte, se fusionarán pronto. –