Doctrine - Hydrate collection in Entity class Doctrine - Hydrate collection in Entity class symfony symfony

Doctrine - Hydrate collection in Entity class


You'll need to write your own hydration method

You have a cyclical reference where one of those nodes (Device) will force a FETCH EAGER. Making matters worse, one of those nodes (Gateway) is acting like ManyToMany join table between the other two, resulting in FETCH EAGER loading everything in an near-infinite loop (or at least large blocks of related data).

 +──<   OneToMany >──+   ManyToOne >──<   ManyToMany +──+   OneToOne       ┌──────< Gateway >──────┐       │                       │       +                       +     Event +──────────────< Device*

As you can see, when device does an EAGER fetch, it will collect many Gateways, thus many Events, thus many Devices, thus many more Gateways, etc. Fetch EAGER will keep going until all references are populated.

Prevent "EAGER" Hydration, by building your own Hydrator.

Building your own hydrator will require some careful data-manipulation, but will likely be somewhat simple for your use case. Remember to register your hydrator with Doctrine, and pass it as an argument to $query->execute([], 'GatewayHydrator');

class GatewayHydrator extends DefaultEntityHydrator{    public function hydrateResultSet($stmt)    {        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);        $class = $this->em->getClassMetadata(Gateway::class);        $gateway = $class->newInstance();        $gateway->setName($data[0]['gateway_name']); // example only        return $gateway;    }}

Alternatively, Remove the Mapped Field from Device to Gateway

Removing the $gateway => Gateway mapping from Device, and mappedBy="gateway" from the Gateway->device mapping, Device would effectively become a leaf from Doctrine's perspective. This would avoid that reference loop, with one drawback: the Device->gateway property would have to be manually set (perhaps in the Gateway and Event setDevice methods).


I'd suggest you couple of options to consider here.

1) As per Doctrine's documentation you can use fetch="EAGER" to hint Doctrine that you want the relation eagerly fetched whenever the entity is being loaded:

/** * @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway", fetch="EAGER") */protected $devices;

If used carefully this can save you from firing additional queries upon iteration but has it's own drawbacks either.

If you start to extensively use forced eager loading you may find yourself in situation where loading an entity to read a simple attribute from it will result in loading tens and even hundreds of relations. This may not look as bad from SQL point of view (perhaps a single query) but remember that all the results will be hydrated as objects and attached to the Unit Of Work to monitor them for changes.

2) If you are using this for reporting purposes (e.g. display all events for a device) then it's better not to use entities at all but to request array hydration from Doctrine. In this case you will be able to control what gets into the result by explicitly joining the relation (or not). As an added benefit you'll skip the expensive hydration and monitoring by the UoM as it's unlikely to modify entities in such a case. This is considered a "best practice" too when using Doctrine for reporting.