Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eager load hasMany & belongsTo (circular reference/infinite loop)

UPDATE (SOLUTION)

  • If you need ->user relationship from one of the $image inside $user->images, then $user variable is already available cause you loaded the ->images from it!
  • Don't use protected $with Eloquent property. It's an anti-pattern.
  • Instead explicitly eager load relationships on-demand from where/when it's needed (Note: it should not prevent you to keep things DRY!)
  • If you really need/want to, see @nicksonyap answer. It does the trick (I believe – not tested).

ORIGINAL

I'm running into what I believe is a simple problem:

  • I have a User object that has many Images
  • Image belongs to User... (inverse relation)

My problem is that I want to eager load both the images() on the User model and the user() on the Image model. To do so, I just setup a $with property as explained in the docs.

My User model:

class User extends EloquentModel {
    protected $with = ['images'];

    public function images()
    {
        return $this->hasMany(Image::class);
    }
}

My Image model:

class Image extends EloquentModel {
    protected $with = ['user'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

But when performing:

$user = User::find(203);

This results in an infinite loop (php segmentation fault). There must be some kind of circular reference that I am not able to locate:

[1]    85728 segmentation fault

EDIT 2016/02

This is the simplest "Workaround" I found:

// User.php
public function setRelation($relation, $value)
{
    if ($relation === 'images') {
        foreach ($value as $image) {
            $image->setUser($this);
        }
    }
    return parent::setRelation($relation, $value);
}
like image 279
eightyfive Avatar asked Jan 25 '16 14:01

eightyfive


People also ask

What is eager load?

Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query. Eager loading is achieved by the use of the Include method. It means that requesting related data be returned along with query results from the database.

What is difference between eager and lazy loading Laravel?

While lazy loading delays the initialization of a resource, eager loading initializes or loads a resource as soon as the code is executed. Eager loading also involves pre-loading related entities referenced by a resource.

What is Laravel eager loading?

Eager loading is super simple using Laravel and basically prevents you from encountering the N+1 problem with your data. This problem is caused by making N+1 queries to the database, where N is the number of items being fetched from the database.

What is eager loading and lazy loading in Laravel?

Relationship Methods Vs.Dynamic properties are "lazy loading", meaning they will only load their relationship data when you actually access them. Because of this, developers often use eager loading to pre-load relationships they know will be accessed after loading the model.


2 Answers

There is a without() method: https://laravel.com/api/5.8/Illuminate/Database/Eloquent/Builder.html#method_without

Placing without() on both sides of a relationship worked.

class Property extends EloquentModel {
    protected $with = ['images'];

    public function images()
    {
        return $this->hasMany(Image::class)->without('property');
    }
}
class Image extends EloquentModel {
    protected $with = ['property'];

    public function property()
    {
        return $this->belongsTo(Property::class)->without('images');
    }

    public function getAlt()
    {
        return $this->property->title;
    }
}

UPDATE:

Even though using without() easily avoid the infinite loop issue, through years of experience with Laravel I realize it is bad practice to set $with in the model as it causes relationships to always load. Hence leading to circular reference/infinite loop

Rather, always use with() to explicitly specify necessary relationships to be eager loaded, however deep necessary (relationship of relationship)

For example:

$user = User::with('images' => function ($query) {
            $query->with('property' => function ($query) {
                $query->with('deeperifneeded' => function ($query) {
                    //...
                });
            });
        ]);

Note: May need to remove without()

like image 63
Nickson Yap Avatar answered Oct 14 '22 14:10

Nickson Yap


When you try to find a Property, that property eager loads all the images it has and every Image eager loads the property it belongs to, which is the property you try to find, which will again start to eager load all the images it has. etc...

The way I would resolve this problem is by not eager loading inside the models, but by eager loading when calling the models.

so using the following:

$prop = Property::with('images')->find(203);

while removing this line in the Property model:

protected $with = ['images'];

And this line in the Image model:

protected $with = ['property'];

I hope this solution works for you.

like image 43
Jens Mombaerts Avatar answered Oct 14 '22 13:10

Jens Mombaerts