Related Objects not Hydrated at all despite fetch:EAGER #7307

Closed
opened 2026-01-22 15:49:40 +01:00 by admin · 6 comments
Owner

Originally created by @craigh on GitHub (Jan 31, 2024).

Bug Report

Q A
BC Break maybe
Version 2.17.4

Summary

No matter how "EAGER" the query, I keep getting a proxy object.

Current behavior

I have a query like

        $qb = $this->createQueryBuilder('record')
            ->innerJoin('record.assignment', 'assignment')
            ->innerJoin('assignment.certification', 'certification')
            ->innerJoin('record.boardMember', 'board_member')
            ->select('record', 'assignment', 'certification', 'board_member');

        return $qb
            ->andWhere('certification.client = :client')
            ->setParameter('client', $client)
            ->orderBy('record.createdAt', 'DESC')
            ->addOrderBy('record.boardMember')
            ->getQuery()
            ->getResult();

The record object (obviously) has relations

    #[ORM\ManyToOne(fetch: 'EAGER')]
    #[ORM\JoinColumn(nullable: false)]
    private ?BoardMember $boardMember = null;

    #[ORM\ManyToOne(fetch: 'EAGER', inversedBy: 'records')]
    private ?CertificationAssignment $assignment = null;

If I run the query and dump the results

        $records = $recordRepository->findAllByClient($filterRequest);
        dd($records);

The BoardMember object in each Record object is a Proxy and that Proxy has NO VALUES. Additionally, the proxy doesn't fetch the values when they are called. The values are all simply unset.

Screenshot 2024-01-31 at 4 50 52 PM

I am using this via Symfony 6.4.3 and I have configured Symfony doctrine/doctrine-bundle 2.11.1 like so:

    orm:
        auto_generate_proxy_classes: true
        enable_lazy_ghost_objects: true
        report_fields_where_declared: true
        # other stuff...

I also tried

            ->setFetchMode(BoardMember::class, 'board_member', ClassMetadataInfo::FETCH_EAGER)

in the query, but that made no change.

I also tried

            ->getArrayResult();

instead of getResult and the arrays are fully hydrated correctly but I would prefer the objects...

This worked previously, with Symfony 5 ( I know you're not Symfony) but the enable_lazy_ghost_objects value changed for that from false to true so may be related. Changing this back to false does seem to fix my problem (Object is still a proxy but has needed values in it). But then I have the depreciation warnings again. So I'd like to fix the other problem...

Expected behavior

I expect the object to by fully hydrated since the fetch:EAGER property is set. I would also expect the Proxy to fetch the value if I called it.

Originally created by @craigh on GitHub (Jan 31, 2024). ### Bug Report <!-- Fill in the relevant information below to help triage your issue. --> | Q | A |------------ | ------ | BC Break | maybe | Version | 2.17.4 #### Summary No matter how "EAGER" the query, I keep getting a proxy object. #### Current behavior I have a query like ```php $qb = $this->createQueryBuilder('record') ->innerJoin('record.assignment', 'assignment') ->innerJoin('assignment.certification', 'certification') ->innerJoin('record.boardMember', 'board_member') ->select('record', 'assignment', 'certification', 'board_member'); return $qb ->andWhere('certification.client = :client') ->setParameter('client', $client) ->orderBy('record.createdAt', 'DESC') ->addOrderBy('record.boardMember') ->getQuery() ->getResult(); ``` The `record` object (obviously) has relations ```php #[ORM\ManyToOne(fetch: 'EAGER')] #[ORM\JoinColumn(nullable: false)] private ?BoardMember $boardMember = null; #[ORM\ManyToOne(fetch: 'EAGER', inversedBy: 'records')] private ?CertificationAssignment $assignment = null; ``` If I run the query and dump the results ```php $records = $recordRepository->findAllByClient($filterRequest); dd($records); ``` The BoardMember object in each Record object is a **Proxy** and that Proxy has NO VALUES. Additionally, the proxy **doesn't fetch the values** when they are called. The values are all simply `unset`. <img width="499" alt="Screenshot 2024-01-31 at 4 50 52 PM" src="https://github.com/doctrine/orm/assets/350048/f6716f99-2440-4617-9f11-ed212e340549"> I am using this via Symfony 6.4.3 and I have configured Symfony doctrine/doctrine-bundle 2.11.1 like so: ```yaml orm: auto_generate_proxy_classes: true enable_lazy_ghost_objects: true report_fields_where_declared: true # other stuff... ``` I also tried ```php ->setFetchMode(BoardMember::class, 'board_member', ClassMetadataInfo::FETCH_EAGER) ``` in the query, but that made no change. I also tried ```php ->getArrayResult(); ``` instead of `getResult` and the arrays are fully hydrated _correctly_ but I would prefer the objects... This worked previously, with Symfony 5 ( I know you're not Symfony) but the `enable_lazy_ghost_objects` value changed for that from `false` to `true` so may be related. Changing this back to `false` **does seem to fix my problem** (Object is still a proxy but has needed values in it). But then I have the depreciation warnings again. So I'd like to fix the other problem... #### Expected behavior I expect the object to by fully hydrated since the `fetch:EAGER` property is set. I would also expect the Proxy to fetch the value if I called it.
admin closed this issue 2026-01-22 15:49:40 +01:00
Author
Owner

@wmouwen commented on GitHub (Feb 26, 2024):

Loosely relates to #11185

@wmouwen commented on GitHub (Feb 26, 2024): Loosely relates to #11185
Author
Owner

@stof commented on GitHub (Mar 6, 2024):

If a proxy object already exists in the identity map of the ORM, you will still get it (even though it might be an initialized proxy at some point) because the identity map must only manage one object instance for each database id (by design), and it cannot replace it by a different one (as it does not have any way to replace the other references to that object in other entities already instantiated). That's why you might still get a proxy object

@stof commented on GitHub (Mar 6, 2024): If a proxy object already exists in the identity map of the ORM, you will still get it (even though it might be an _initialized_ proxy at some point) because the identity map must only manage one object instance for each database id (by design), and it cannot replace it by a different one (as it does not have any way to replace the other references to that object in other entities already instantiated). That's why you might still get a proxy object
Author
Owner

@craigh commented on GitHub (Mar 6, 2024):

@stof are you saying that if I have two fetch actions for that entity, I might be creating a proxy in the first and then on the second fetch getting that same proxy instead of the fully hydrated object? (and thank you for answering, I still have not solved this)

@craigh commented on GitHub (Mar 6, 2024): @stof are you saying that if I have two `fetch` actions for that entity, I might be creating a proxy in the first and then on the second fetch getting that same proxy instead of the fully hydrated object? (and **thank you** for answering, I still have not solved this)
Author
Owner

@stof commented on GitHub (Mar 7, 2024):

@craigh the second fetch will indeed get the same object instance (whether it gets hydrated or no during the second fetch depends on the case).

@stof commented on GitHub (Mar 7, 2024): @craigh the second fetch will indeed get the same object instance (whether it gets hydrated or no during the second fetch depends on the case).
Author
Owner

@linuxprocess commented on GitHub (Oct 15, 2024):

Hi @craigh
You can try to force doctrine to fetch all join entities in your createQueryBuilder with addSelect() like this :

$this->createQueryBuilder('d')
            ->addSelect('r')
            ->join('d.dsbReference','r')
@linuxprocess commented on GitHub (Oct 15, 2024): Hi @craigh You can try to force doctrine to fetch all join entities in your createQueryBuilder with addSelect() like this : ``` $this->createQueryBuilder('d') ->addSelect('r') ->join('d.dsbReference','r') ```
Author
Owner

@philepsybo commented on GitHub (Nov 19, 2025):

If a proxy object already exists in the identity map of the ORM, you will still get it (even though it might be an initialized proxy at some point) because the identity map must only manage one object instance for each database id (by design), and it cannot replace it by a different one (as it does not have any way to replace the other references to that object in other entities already instantiated). That's why you might still get a proxy object

This hit me quite unexpectedly today I gotta admit. It would be nice to be able to trigger eager-fetching through the query builder without having to join everything manually. In some cases simply configuring it through the entity alone is not really flexible enough.
I often deal with such issues in workflow-event-listeners.

@philepsybo commented on GitHub (Nov 19, 2025): > If a proxy object already exists in the identity map of the ORM, you will still get it (even though it might be an _initialized_ proxy at some point) because the identity map must only manage one object instance for each database id (by design), and it cannot replace it by a different one (as it does not have any way to replace the other references to that object in other entities already instantiated). That's why you might still get a proxy object This hit me quite unexpectedly today I gotta admit. It would be nice to be able to trigger eager-fetching through the query builder without having to join everything manually. In some cases simply configuring it through the entity alone is not really flexible enough. I often deal with such issues in workflow-event-listeners.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7307