简体   繁体   中英

Lazy load between referenced documents on MongoDB with Doctrine ODM

Helo,

firstly, please excuse my English, is not very good.

I am migrating the data container of a Symfony2 application to MongoDB, before that it run with MySQL.

I added the DoctrineMongoDBBundle and "almost everything" works perfectly.

I have some references between documents in which I would like to keep the "lazy load" pattern offered by the Doctrine ORM. I have read the official documentation for Doctrine ODM,

and some examples that explain how to create relations and define documents to get "lazy load" behavior,

but I can not make it work.

In my case, I have two documents, "travel" and "note" with a 1:N relationship I want to keep, something like this:

<?php

namespace MyApp\TravelBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * Travel
 *
 * @ODM\Document(collection="travel")
 */
class Travel {

    /**
     * @var \MyApp\NoteBundle\Document\Note
     * 
     * @ODM\ReferenceMany(targetDocument="\MyApp\NoteBundle\Document\Note", mappedBy="travel", sort={"createdAt"="asc"} )
     */
    private $notes;

    // more properties ...

    public function __construct() {
        $this->notes = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add notes
     *
     * @param \MyApp\NoteBundle\Document\Note $notes
     */
    public function addNote(\MyApp\NoteBundle\Document\Note $notes) {
        $this->notes[] = $notes;
    }

    /**
     * Remove notes
     *
     * @param \MyApp\NoteBundle\Document\Note $notes
     */
    public function removeNote(\MyApp\NoteBundle\Document\Note $notes) {
        $this->notes->removeElement($notes);
    }

    /**
     * Get notes
     *
     * @return Doctrine\Common\Collections\Collection $notes
     */
    public function getNotes() {
        return $this->notes;
    }

    // more methods ...

}
?>

<?php

namespace MyApp\NoteBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * Note
 *
 * @ODM\Document(collection="note")
 */
class Note
{
    /**
     * @var \MyApp\TravelBundle\Document\Travel
     * 
     * @ODM\ReferenceOne(targetDocument="MyApp\TravelBundle\Document\Travel", inversedBy="notes")
     */
    private $travel;

    // more properties ...

    /**
     * Set travel
     *
     * @param \MyApp\TravelBundle\Document\Travel $travel
     * @return Note
     */
    public function setTravel(\MyApp\TravelBundle\Document\Travel $travel) {
        $this->travel = $travel;
        $travel->addNote($this);

        return $this;
    }

    // more methods ...

}
?>

When I add a note to a travel I understand that the result for the travel document should be:

{ "_id" : ObjectId( "5183aa63095a1a3921000000" ),
  "name" : "First travel",
  "isActive" : true,
  "createdAt" : Date( 1367583331000 ),
  "updatedAt" : Date( 1367583331000 ),
  "notes" : [{ "$ref" : "note",
    "$id" : ObjectId( "5183aa63095a1a3955000000" ),
    "$db" : "mydb" }]
 }

and for the note document should be:

{ "_id" : ObjectId( "5183aa63095a1a3955000000" ),
  "travel" : { "$ref" : "travel",
    "$id" : ObjectId( "5183aa63095a1a3921000000" ),
    "$db" : "mydb" },
  "note" : "First note",
  "createdAt" : Date( 1367583331000 ),
  "updatedAt" : Date( 1367583331000 ) }

but for now I only get a reference in the note document, whilst no reference appears in the travel document, and when I do a query in the travel document Doctrine do not load the related note documents:

<?php
.
.
$travel = $dm->getRepository('TravelBundle:Travel')->findCurrentTravel($user->getId());
$travel->getNotes(); // IS EMPTY :(
.
.
?>

The process I follow to add a note to a travel is as follows:

<?php

namespace MyApp\TravelBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class TravelController extends Controller {

    public function createNoteAction(Request $request) {
        $dm = $this->get('doctrine.odm.mongodb.document_manager');
        $travel = $dm->getRepository('TravelBundle:Travel')->findCurrentTravel($user->getId());
        $entity = new Note();
        $form = $this->createForm(newNoteType(), $entity);
        if ($request->isMethod('POST')) {
            $form->bind($request);
            if ($form->isValid()) {
                $entity->setTravel($travel);
                $dm>persist($travel);
                $dm>persist($entity);
                $dm>flush();
            }
        }
    }
}
?>

Any ideas or suggestions to get the method $travel->getNotes() can automatically retrieve referenced notes through the "lazy load".

Thank you very much beforehand for the contributions,

Zacarías

What you want to achieve is done by simply removing the mappedBy attribute in the ReferenceOne of the $travel property:

@ODM\ReferenceMany(targetDocument="\MyApp\NoteBundle\Document\Note", sort={"createdAt"="asc"} )

In this way doctrine will store the Notes IDs in the $nodes array.


With "mappedBy" instead, Doctrine doesn't store the IDs of the Notes in the $notes array, but instead it will do a query like this to fetch the actual notes:

db.Notes.find({travel.$id: <travelId>});

Note that IMHO this one is the preferred approach , since in this way when you add/remove a note, you don't have to update the Travel doc. (however you'll have to add an index on the $travel field)

Also notice that with ReferenceMany , using the mappedBy is lazy: only when you try to cycle the $notes array it will actually execute the query, so it is also lightweight.

See the doc for more info.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM