Laravel全局作用域与关联检查:Undefined property报错及解决
问题:根据用户关联的Provider全局限制可查看内容时出现未定义属性错误
我想根据用户所属的Provider模型限制其可查看内容,为此定义了全局作用域:
Provider模型代码:
class Provider extends Model { protected static function booted() { static::addGlobalScope(new ProviderScope); } ... }
作用域定义代码:
class ProviderScope implements Scope { public function apply(Builder $builder, Model $model) { if(auth()->user()->providers->isNotEmpty()) { $builder->whereIn('id', auth()->user()->providers->pluck('id')); } } }
运行时出现报错:Undefined property: App\Models\User::$providers,但该关联在应用其他场景均正常工作。
我猜测是在Provider模型的booted方法中调用auth()->user()->providers时,会触发providers初始化,进而再次调用booted方法形成循环?但疑惑为何报错是未定义属性而非无限循环。请问我的猜测是否正确?另外,针对“根据用户已关联的Provider全局限制其可查看的Provider”这一需求,该如何解决?
回答
1. 你的猜测是否正确?
你的方向有一定合理性,但并非典型的无限循环问题。实际原因是:
- 当
booted方法触发全局作用域时,调用auth()->user()->providers会加载用户的Provider关联,而加载关联时会再次触发Provider模型的查询,此时全局作用域已生效,又会去调用auth()->user()->providers,形成间接循环查询链。 - 报错显示“未定义属性”,是因为循环查询过程中,Laravel的模型实例可能处于未完全初始化的状态,导致关联属性暂时无法被识别,而非常规的无限循环内存溢出或超时报错。
2. 解决方案
要实现需求,需避免在Provider全局作用域中直接加载用户的providers关联(防止循环查询),以下两种方案可行:
方案一:直接查询关联表,绕开模型关联加载
class ProviderScope implements Scope { public function apply(Builder $builder, Model $model) { $user = auth()->user(); if ($user) { // 使用查询构造器直接查关联表,避免触发Provider模型的全局作用域 $providerIds = $user->providers()->pluck('providers.id'); if ($providerIds->isNotEmpty()) { $builder->whereIn('id', $providerIds); } } } }
这里用$user->providers()(返回查询构造器)代替$user->providers(加载后的关联集合),只会执行一次关联表的查询,不会触发Provider的全局作用域循环。
方案二:临时移除全局作用域,避免循环触发
class ProviderScope implements Scope { public function apply(Builder $builder, Model $model) { $user = auth()->user(); if (!$user) { return; } // 临时移除当前全局作用域,加载用户关联时不会触发循环 $builder->withoutGlobalScope($this); $providerIds = $user->providers->pluck('id'); if ($providerIds->isNotEmpty()) { // 重新添加作用域并应用过滤条件 $builder->withGlobalScope($this, $this)->whereIn('id', $providerIds); } } }
这种方式先临时移除全局作用域,加载完用户的providers关联后,再重新添加作用域并设置过滤条件。
额外注意事项
- 必须添加
auth()->user()的空值判断,避免未登录用户触发错误。 - 控制台命令、队列任务等无登录用户的场景,
auth()->user()会返回null,需单独处理。
内容的提问来源于stack exchange,提问作者Jeremy Belolo
相关产品推荐
相关产品推荐

