Eager load hasmany & belongsTo (循环引用/无限循环)



UPDATE (SOLUTION)

  • 如果您需要$user->images内部$image之一->user关系,那么$user变量已经可用,因为您从中加载了->images
  • 不要使用protected $with Eloquent 属性。这是一种反模式。
  • 相反,明确地渴望从需要的地方/时间按需加载关系(注意:它不应该阻止你保持干燥!
  • 如果您确实需要/想要,请参阅@nicksonyap答案。它确实可以解决问题(我相信 - 未经测试)。

源语言

我遇到了我认为是一个简单的问题:

  • 我有一个User对象,它有很多Image
  • Image属于User...(反比关系)

我的问题是我想急切地加载User模型上的images()Image模型上的user()。为此,我只需按照文档中的说明设置一个$with属性。

我的User模型:

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

我的Image模型:

class Image extends EloquentModel {
    protected $with = ['user'];
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

但是在表演时:

$user = User::find(203);

这会导致无限循环(php 分段错误)。一定有某种我无法找到的循环引用:

[1]    85728 segmentation fault

编辑 2016/02

这是我发现的最简单的"解决方法":

// User.php
public function setRelation($relation, $value)
{
    if ($relation === 'images') {
        foreach ($value as $image) {
            $image->setUser($this);
        }
    }
    return parent::setRelation($relation, $value);
}

有一个without()方法:https://laravel.com/api/5.8/Illuminate/Database/Eloquent/Builder.html#method_without

without()放在关系的双方是有效的。

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;
    }
}

更新:

尽管使用without()可以轻松避免无限循环问题,但通过多年的Laravel经验,我意识到在模型中设置$with是一种不好的做法,因为它会导致关系始终加载。因此导致循环引用/无限循环

相反,始终使用with()来明确指定要急切加载的必要关系,无论需要多么深(关系关系)

例如:

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

注意:可能需要删除without()

当您尝试查找属性时,该属性会预先加载它拥有的所有图像,并且每个图像都渴望加载它所属的属性,这是您尝试查找的属性,它将再次开始预先加载它拥有的所有图像。 等等...

我解决此问题的方法是不是在模型内部预先加载,而是在调用模型时通过预先加载。

因此,请使用以下内容:

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

在属性模型中删除此行时:

protected $with = ['images'];

图像模型中的这一行:

protected $with = ['property'];

我希望这个解决方案对您有用。