Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine ODM OneToOne Bi-Directional Reference using repositoryMethod

How can Doctrine ODM be used to create a one-to-one bi-directional reference that lazy loads while using a field other than the primary key for the reference?

I have two collections in MongoDB with documents, Article and ArticleMetaData. For every Article document, there is an ArticleMetaData and vise versa. (A OneToOne Bi-Directional relationship.) For legacy reasons, the two document types need to be in separate collections. Both collections are updated by external systems that have no knowledge about the Mongo IDs. They do however contain a shared field "groupcode" which can be used to match the right article to its meta data.

I try to configure Doctrine in such a way that I can get the meta data for an article object and an article from its meta data object but I want to keep them lazy loaded. (There is no need to query for the other end when I don't need it.)

The mappings look as follows:

Foo\BarBundle\Document\Article:
    repositoryClass: Foo\BarBundle\Repository\ArticleRepository
    changeTrackingPolicy: DEFERRED_EXPLICIT
    collection: article
    type: document
    fields:
        id:
            id: true
        groupcode:
            type: int
            index: true
            unique:
                order: asc
        ...
    referenceOne:
        metaData:
            targetDocument: Foo\BarBundle\Document\ArticleMetaData
            mappedBy: groupcode
            repositoryMethod: findOneByArticle

Foo\BarBundle\Document\ArticleMetaData:
    repositoryClass: Foo\BarBundle\Repository\ArticleMetaDataRepository
    changeTrackingPolicy: DEFERRED_EXPLICIT
    collection: article_meta
    fields:
        id:
            id: true
        groupcode:
            type: int
            index: true
            unique:
                order: asc
        ...
    referenceOne:
        article:
            targetDocument: Foo\BarBundle\Document\Article
            mappedBy: groupcode
            repositoryMethod: findOneByMetaData

And the repository methods mentioned above:

// In the ArticleRepository
public function findOneByMetaData(ArticleMetaData $metadata)
{
    $article = $this
        ->createQueryBuilder()
        ->field('groupcode')->equals($metadata->getGroupcode())
        ->getQuery()
        ->getSingleResult();

    $article->setMetaData($metadata);

    return $article;
}

// In the ArticleMetaDataRepository
public function findOneByArticle(Article $article)
{
    $metaData = $this
        ->createQueryBuilder()
        ->field('groupcode')->equals($article->getGroupcode())
        ->getQuery()
        ->getSingleResult();

    $metaData->setArticle($article);

    return $metaData;
}

It all seems to work quite well. I can query an Article or ArticleMetaData and get the other side, only the problem is: it does not seem to lazy load. When I query for an Article:

$article = $documentManager
    ->getRepository('FooBarBundle:Article')
    ->findOneBy(['groupcode' => 123]);

A lot of queries are executed:

doctrine.INFO: MongoDB query: {"find":true,"query":{"groupcode":123},"fields":[],"db":"development","collection":"article"}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":1,"query":{"groupcode":123},"fields":[]}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":null,"query":{"groupcode":123},"fields":[]}
doctrine.INFO: MongoDB query: {"find":true,"query":{"groupcode":123},"fields":[],"db":"development","collection":"article_meta"}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":1,"query":{"groupcode":123},"fields":[]}
doctrine.INFO: MongoDB query: {"find":true,"query":{"groupcode":123},"fields":[],"db":"development","collection":"article"}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":1,"query":{"groupcode":123},"fields":[]}

What am I doing wrong? Is there I way I can accomplish a lazy loading one-to-one bi-directional reference having the above constraints?

Edit:

After reading Rob Holmes' answer I removed a test in the repository methods that could have caused the issue. Unfortunately the problem still remains and there are still 3 queries being executed where one (or two at most) suffice.

like image 509
Xatoo Avatar asked Jan 08 '15 14:01

Xatoo


1 Answers

Doctrine ODM will already lazy load the referenced document, rather than prefetch it for you.

I believe your problem actually lies in your repository methods... For example, in the findOneByMetaData function the first thing you are doing is calling $metadata->getArticle() in doing this you are asking doctrine to load the article from the database, which due to your repositoryMethod is going to call findOneByMetaData again. This is why you are seeing multiple queries.

Your findOneByMetaData function should look more like this:

// In the ArticleRepository
public function findOneByMetaData(ArticleMetaData $metadata)
{
    $article = $this->createQueryBuilder()
        ->field('groupcode')->equals($metadata->getGroupcode())
        ->getQuery()
        ->getSingleResult();

    $article->setMetaData($metadata);

    return $article;
}

Doctrine will take care of whether the article has been loaded yet, so there is no need to try and check for a null value. The same also applies to your findOneByArticle function too.

Hope this make sense, and helps you resolve your issue.

like image 84
Rob Holmes Avatar answered Sep 18 '22 21:09

Rob Holmes