2

Imagine the following situation. You have books:

Book(bookId, authorId, title)

and authors:

Author(authorId, name)

and each book has (for the sake of simplicity) a single author.

By default all associations are configured in lazy mode. So, if I have the scenario, when I first load all books, iterate over the collection and fetch author of each book, I'll perform lots of queries to the database.

$books = $this->getDoctrine()
        ->getRepository('AppBundle:Book')
        ->findAll();

foreach($books as $b) {
    echo $b->getAuthor()->getName();
}

Can I programmatically ask Doctrine to load authors eagerly for this specific query (not globally via configuration)?

Related: In Doctrine 2 can the Fetch Mode (Eager/Lazy etc.) be changed at runtime?

Related: http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/tutorials/getting-started.html#list-of-bugs

Community
  • 1
  • 1
Denis Kulagin
  • 7,244
  • 13
  • 50
  • 104
  • 2
    Easiest way is to just join in the authors relation, preferably by creating your own ´findAllWithAuthors` method in the Book repository, or similar. I wrote up an answer for a similar question here http://stackoverflow.com/questions/36250248/symfony2-count-entity-fields-that-relate-to-another-entity/36253620#36253620 – JimL Jul 04 '16 at 18:55
  • 1
    @JimL Omg... is it really so hard to do in Doctrine?! – Denis Kulagin Jul 04 '16 at 19:07
  • I wouldn't really call it hard. Having the queries in repositories is just considered best practice. You can easily just create the query in the controller as well. The standard easy methods (find, findby, etc) are generally just to get you started. Very few will hold up in any real application / domain logic. – JimL Jul 04 '16 at 19:09
  • 1
    `$books = $this->getDoctrine()->createQueryBuilder()->select(['b', 'a'])->from('AppBundle:Book', 'b')->join('b.authors', a')->getQuery()->getResult();` – JimL Jul 04 '16 at 19:12
  • 1
    or in a BookRepository method `return $this->_em->createQueryBuilder('b')->addSelect('a')->join('b.authors', 'a')->getQuery->getResult();` – JimL Jul 04 '16 at 19:15
  • Got it. I am just new to the Symfony, had worked with _Yii_ previously. There you write: _Book::model()->with('author')->findAll()_ and that's it! I believe learning deeper into Doctrine will make me understand why things are orginized there the other way. – Denis Kulagin Jul 04 '16 at 19:18
  • Everything has their pros and cons. It seems Yii has a more Active Record style ORM, compared to Doctrine2 that is a Data Mapper pattern. The biggest difference is that with AR the entities themselves handle persisting, relations, etc. In Data Mapper you use a separate manager for it. – JimL Jul 04 '16 at 19:21
  • I see your point. Will read on differences between Data Mapping approach and AR approach to grasp the ideology behind former. – Denis Kulagin Jul 04 '16 at 20:03
  • http://stackoverflow.com/a/6949275/2270041 – Matteo Jul 04 '16 at 20:11
  • @Tnx, definetelly related, but I haven't found it while doing my own research on the question. – Denis Kulagin Jul 04 '16 at 20:16

2 Answers2

1

You can simply mark the association between books and authors as EAGER (versus the implicit default of LAZY) and Doctrine will always load that particular association up front.

This can be accomplished by adding:

fetch=EAGER

to the mapping association.

One potential method to do this at runtime would be to create a Mapped Superclass. The superclass would define your relationships and other parts of your associations (not the relationship you're trying to adjust).

Then, to actually use the class at run time, you could create two other concrete implementations: LazyBook and EagerBook. Depending on your scenario at runtime, you would use one or the other of these concrete implementation entities to construct your associations.

Of course, LazyBook would define your Book -> Author association as a LAZY one (either explicitly or implicitly) and EagerBook would define it as EAGER.

This isn't truly dynamic as you've defined, but it allows you to programmatically determine which association to use at any given time while also self-documenting that it could be either.

rockerest
  • 9,847
  • 3
  • 33
  • 67
  • 4
    I am asking specifically about **programmatic way** to be more flexible with the queries. But thanks for offering this option! – Denis Kulagin Jul 04 '16 at 21:04
  • @DenisKulagin Hmm, okay. I guess I'm not really seeing the difference between "writing it in the software" and "programmatic". Do they not accomplish the same goal? Or is the goal to have a single association that is *either* LAZY or EAGER? If _that's_ the goal, I'm also not understanding the use-case. – rockerest Jul 04 '16 at 21:15
  • Yep, the difference is that in your case you can't switch at runtime. Actually it's not so much about the use-cases, but more about flexibility. I like to tune each query, so it loads the exact amount of data needed. No less, but no more as well. – Denis Kulagin Jul 04 '16 at 21:45
  • @DenisKulagin I've added some information about Mapped Superclasses that may hit all the important points (decide at runtime, primarily). – rockerest Jul 04 '16 at 22:47
-2

One very important thing to understand here, is that Doctrine uses the Data Mapper pattern and not the Active Record pattern (you can find it in the Yii framework for example):

Doctrine 2 is an object-relational mapper (ORM) for PHP 5.4+ that provides transparent persistence for PHP objects. It uses the Data Mapper pattern at the heart, aiming for a complete separation of your domain/business logic from the persistence in a relational database management system.

The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about persistence only as a secondary problem. This doesn’t mean persistence is downplayed by Doctrine 2, however it is our belief that there are considerable benefits for object-oriented programming if persistence and entities are kept separated.

http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/tutorials/getting-started.html#what-is-doctrine

That essentially means, that entity classes do not know a single thing about how they are persisted to the database. Even though they can have comment type annotations on them, those are just a form of metadata processed by ORM.

In turn that means you can do the very same thing you did with ActiveRecord, but it's now done at just another place. Let's take a look at the difference:

In ActiveRecord-based ORM (like Yii):

$books = Book::model()->with('author')->findAll();

In DataMapper-based ORM (like Symfony/Doctrine):

$books = $this->getDoctrine()->createQueryBuilder()
  ->select(['b', 'a'])
  ->from('AppBundle:Book', 'b')
  ->join('b.author', a')
  ->addSelect('a')
  ->getQuery()
  ->getResult();

Small comment on the later. The query you are building there is not an SQL query, but rather a DQL query (object-query-language employed by Doctrine).

So join/addSelect in here is much like with at the former query just telling ORM engine that you would like to load author at the same query. Particular relation metadata (e.g. column names for both underlying tables) is still defined out there at the entities metadata level.

Syntax (select, from, join) resembles the SQL on purpose, but you shouldn't be confused by it. Here, building the query, you operate ORM entities and not the database columns/tables.

Denis Kulagin
  • 7,244
  • 13
  • 50
  • 104