Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
986 views
in Technique[技术] by (71.8m points)

laravel 5 - 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);
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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()


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...