Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 - Check if a Doctrine Entity Association has been Initialized/Loaded without Triggering Lazyload

I have an entity called foo which has an OneToMany association with an entity called bar that is accessible as $foo->getBar() (an ArrayCollection). Normally calling $foo->getBar() would trigger a Lazy Loading of associated bar entities (if they weren't joined originally).

How can I check if bar has been loaded without triggering a Lazy Load? I don't need the associated entities, if they weren't loaded originally, and I don't want them to load, I just want to know IF they were loaded.

Example

In the fooRepository I have a method called getFooWithBar() and that has a join which loads all the bars as an ArrayCollection and returns foo with all the associated bar entities. But if I just call a simpler method like getFooById() with a simple query, the bar entities were not loaded with a join, so they are not contained in $foo.

So in another controller I have $foo, and I want to check if getBar() has associated entities loaded yet, but I do not want to trigger the Lazy Loading. If it doesn't have associated entities, I don't want them. I just need to know IF they have been loaded.

NOTE: I also do not want to turn off Lazy Loading on the entity association for all instances.

Method that Doesn't Work for Inverse side of OneToMany

I put this magic getter method in my entity:

public function __get($property) {
    return isset($this->$property) ? $this->$property : null;
}

Which theoretically lets me check if the property is set (or if it's still the default private declaration). And this works when my entity is the owning side. But if it's the inverse side, $this->property is never set. Doctrine does some fancy stuff so that when you do getProperty() it's looking at the data somewhere else. I figured this out because this function works when it's the owning side (it returns a proxy of the associated entity), but it returns null when the associated entity is owned by the other entity.

like image 223
Chadwick Meyer Avatar asked Dec 18 '22 22:12

Chadwick Meyer


2 Answers

After years of testing our code (responding to Doctrine changes) the following is the best solution we could come up with to check if an association has been loaded, WITHOUT trigger LazyLoad. None of this stuff is documented in Doctrine (unfortunately), so you have to look at the source code and/or play with the code.

The Solution

In the end there are many different types of different associations that could be loaded from *ToMany (PersistentCollection) or *ToOne associations (Proxy or direct entity). This means we need to create a method that checks for all the possibilities (that we are currently aware of in our app). We created a trait that we add to all our entities, so we can call $entity->isLoaded($propertyName) to check if it's loaded.

public function isLoaded($property)
{

    // *ToMany Association are PersistentCollection and will have the isInitialized property as true if it's loaded
    if ($this->{$property} instanceof PersistentCollection) {
        return $this->{$property}->isInitialized();
    }
    // *ToOne Associations are (sometimes) Proxy and will be marked as __isInitialized() when they are loaded
    if ($this->{$property} instanceof Proxy) {
        return $this->{$property}->__isInitialized();
    }
    // NOTE: Doctrine Associations will not be ArrayCollections. And they don't implement isInitalized so we really
    // can tell with certainty whether it's initialized or loaded. But if you join entities manually and want to check
    // you will need to set an internal mapper that records when you've loaded them. You could return true if count > 0 
    if ($this->{$property} instanceof ArrayCollection) {
        //  NOTE: __isLoaded[$property] is an internal property we record on the Setter of special properties we know are ArrayCollections
        return (!empty($this->__isLoaded[$property]) || $this->{$property}->count() > 0);
    }

    // NOTE: there are never any Collections that aren't ArrayCollection or PersistentCollection (and it does no good to check because they won't have isInitialized() on them anyway

    // If it's an object after the checks above, we know it's not NULL and thus it is "probably" loaded because we know it's not a Proxy, PersistentCollection or ArrayCollection
    if (is_object($this->{$property})) {
        return true;
    }
    // If it's not null, return true, otherwise false. A null regular property could return false, but it's not an Entity or Collection so indeed it is not loaded.
    return !is_null($this->{$property});
}
like image 91
Chadwick Meyer Avatar answered May 12 '23 21:05

Chadwick Meyer


When you load your foo object, bar will be an instance of Doctrine\ORM\PersistentCollection. You can call the isInitialized() method on this collection to find out if has been initialized.

For Associations that are an ArrayCollection:

$initialized = $foo->getBar()->isInitialized();
like image 22
Carlos Granados Avatar answered May 12 '23 22:05

Carlos Granados