7

I have a simple entity with many-to-many and one-to-many associations. I'm aware of 'Joins' for fetching related associations which is a manual solution for my problem.

How can I fetch an entity with all of its associations using EntityManager in Doctrine2? e.g.:

$this->em
     ->getRepository('Entities\Patientprofile')
     ->findOneByuserid('555555557')
     ->fetchAllAssociations();
Wilt
  • 41,477
  • 12
  • 152
  • 203
Mehdi Fanai
  • 4,021
  • 13
  • 50
  • 75

5 Answers5

27

from http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql

you can set eager fetch mode temporarily:

$query = $em->createQuery("SELECT u FROM MyProject\User u");
$query->setFetchMode("MyProject\User", "address", "EAGER");
$query->execute();

If you want do load dynamically all associations with this fetch mode, you can use the getAssociationMappings() method of the Doctrine\ORM\Mapping\ClassMetadataInfo, passing your entity name as parameter to the constructor of ClassMetadataInfo and then iterate over the returned array as $assoc and call:

$query->setFetchMode("MyProject\User", $assoc, "EAGER");

Doc: ClassMetadataInfo#getAssociationMappings()

Pete
  • 1,305
  • 1
  • 12
  • 36
timaschew
  • 16,254
  • 6
  • 61
  • 78
  • 2
    How deep will this method eager load? Just the objects contained in object U, with any objects in those classes reverting to lazy load? Or is this query going to load anything linked to anything linked to U? – Michael.Lumley Apr 22 '14 at 09:32
  • 1
    @Michael.Lumley Yes, it goes 1-level depth only. Any idea? The only solution is to join all associations manually in DQL? – Sithu Oct 08 '15 at 06:41
  • Thank you, this was right direction for me to override fetch mode set in entity annotaions. I ended up doing it in repository public method (and call it if needed) with `$metadata = $this->getClassMetadata(); $metadata->getAssociationMappings();` Looping through results and overriding fetch with `$metadata->setAssociationOverride($field, ['fetch' => $fetchMode]);` – wormhit Apr 11 '19 at 09:48
18

Doctrine2 setFetchMode not working with "EAGER"

I tried also to fetch the associating entities "eagerly" using setFetchMode in my query, but the following didn't seem to work:

$query->setFetchMode("MyProject\User", "address", "EAGER");

When I jumped into the files I found out that the third parameter $fetchMode should be an integer. The constants are defined in Doctrine\ORM\Mapping:ClassMetadataInfo. When passing a string it will default to Mapping\ClassMetadata::FETCH_LAZY because of this if clause.

/**
 * Specifies that an association is to be fetched when it is first accessed.
 */
const FETCH_LAZY = 2;

/**
 * Specifies that an association is to be fetched when the owner of the
 * association is fetched.
 */
const FETCH_EAGER = 3;

/**
 * Specifies that an association is to be fetched lazy (on first access) and that
 * commands such as Collection#count, Collection#slice are issued directly against
 * the database if the collection is not yet initialized.
 */
const FETCH_EXTRA_LAZY = 4;

So setting the corresponding integer solved the problem:

$query->setFetchMode("MyProject\User", "address", 3);

Or declare the class use Doctrine\ORM\Mapping\ClassMetadata at the top and then use the constant:

$query->setFetchMode("MyProject\User", "address", ClassMetadata::FETCH_EAGER);

EDIT:

Since there seems to be a lot of confusion here on how to fetch associations the right way I will edit my answer and add some additional information on how you can fetch join using your repository.

According to the Doctrine documentation there are 2 types of joins:

  1. Regular Joins: Used to limit the results and/or compute aggregate values.

  2. Fetch Joins: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a query.

So to get an entity including its associations you will need to "fetch-join" all these associations to make sure they are loaded eagerly.

I usually don't use DQL queries for getting entities and solving my fetch joins, instead I add a custom method to a repository where I use a query builder. This is more flexible and much more readable then using DQL. The correct DQL query will be created by the query builder when we call the createQuery method. You can check the created DQL query of course for debug purposes.

An example for such a custom method inside the Patientprofile entity repository from the question above:

public function findPatientByIdWithAssociations($id)(
    // create a query builder for patient with alias 'p'
    $qb = $this->createQueryBuilder('p')
               ->where('p.id = :patient_id')
               ->addSelect('pd')
               ->leftJoin('p.documentation', 'pd')
               ->addSelect('pa')
               ->leftJoin('p.address', 'pa')
               ->setParameter('patient_id', $id);

    $query = $queryBuilder->getQuery();
    return $query->getSingleResult();
}

And now you can use your custom repository method to get the patient by id (for example '555555557') including associations to the patient documentation and address:

$repository = $this->em->getRepository('Entities\Patientprofile');
$patient = $repository->findPatientByIdWithAssociations('555555557');

Make sure you use both addSelect and leftJoin to do eager loading.

Wilt
  • 41,477
  • 12
  • 152
  • 203
  • 1
    Doctrine2 `setFetchMode` with `"EAGER"` works for me. – Sithu Oct 08 '15 at 03:09
  • Well...I found that `join` and `select` just works for EAGER loading. No need to call `setFetchMode()`. – Sithu Oct 08 '15 at 06:31
  • @Sithu the question was about other ways then the manual fetch join in the query. I agree that select+join is the common way to go when you want to get your associations loaded in the result. – Wilt Oct 08 '15 at 06:42
  • 2
    @Wilt Unfortunately, I found that `setFetchMode()` fetches 1-level depth of association. I think the only solution is `select`+`join` to load all associations. Is it possible with `setFetchMode()`? – Sithu Oct 08 '15 at 06:52
  • @Sithu I don't think so. I have to say I never use it myself, I posted an answer because I saw that people here abused (passed wrong parameters to) the method (I also made this mistake myself when testing the method). – Wilt Oct 08 '15 at 07:05
  • @Sithu Be aware that if you pass a string `"EAGER"` [it will default to `Mapping\ClassMetadata::FETCH_LAZY`](https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/AbstractQuery.php#L682) – Wilt Oct 08 '15 at 07:18
  • @Wilt I just noticed that `setFetchMode()` with integer or string did not work either even for 1-level depth of association. I don't know why. So, I had to use `join`+`select`. – Sithu Oct 08 '15 at 08:42
  • @Stphane I suggest you post a question with all details, based on the little information in your comment it will be impossible to say something useful about your specific case. Leave a comment with a link when you created a question then I will take a look... – Wilt Jul 07 '16 at 15:19
  • @Wilt Thank you for your help, just give me some time to post and I will replace this comment with a link to the new thread. – Stphane Jul 07 '16 at 16:22
  • @Wilt [here it is](http://stackoverflow.com/questions/38251881/doctrine-2-5-unexpected-associattion-fetch-behaviour-symfony-3). Again, thank you. – Stphane Jul 07 '16 at 17:40
8

Doctrine 2 uses Proxy classes for lazy loading, so you don't actually need to have the associations' data fetched until you use the objects. Since the Proxy classes inherit from your association classes, you're able to use the proxies exactly as you would use the fretch association classes.

but, if you really need to fetch the actual association classes, you need to tell the query to set the fetch mode to Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER. If you're using the annotations, you can achieve this with:

e.g.

/**
 * @ManyToMany(targetEntity="Item", fetch="EAGER")
 */
private $items;
chriswoodford
  • 1,223
  • 9
  • 8
  • 12
    The thing that sucks about this method is that I want to eager load depending on the context. It seems to be that this will always eager load no matter how you call it. I guess the only other option is to use DQL – MikeMurko Feb 09 '13 at 23:15
  • 1
    So guys, I don't have any idea about why this answer was accepted. I would suggest to avoid in most part of the cases to use `fetch="EAGER"` in your relations. After spent a good amount of time, I did find a good solution here: https://stackoverflow.com/a/53833312/1545939 – FabianoLothor Jul 21 '21 at 03:25
5

You can use a DQL query:

$query = $em->createQuery("SELECT p, f FROM Entities\\Patientprofile p JOIN p.Foo f WHERE p.id = ?1");
$query->setParameter(1, 321);
$patient = $query->getSingleResult();
Maxence
  • 12,868
  • 5
  • 57
  • 69
2

Faced the same problem. It was necessary to pull out all chain of parents of an element. $query->setFetchMode(EntityClass, "alias_in_entity", 3) gets only 1 lvl deep, other parents are just proxy. This can be fixed by changed in entity class fetch mode to eager. But if it`s not if this is not possible for some reason (performance etc), this can be made as @wormhit mentioned by changing entity metadata "on fly"

Example:

$query = $this->entityManager->createQueryBuilder()->select('fields')
            ->from(FormField::class, 'fields');
$metadata = $this->entityManager->getClassMetadata(FormField::class);
$metadata->setAssociationOverride('parent', ['fetch' => \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER]);

return $query->getOneOrNullResult();
timosh
  • 81
  • 8