Eloquent全局作用域中模型未实例化致权限验证失败问题
解决Eloquent全局作用域中权限检查依赖关联关系的问题
这个问题的核心在于全局作用域里的$model是未填充数据的空模型实例——它还没有从数据库加载任何记录,自然也无法访问parentRelation这类关联关系。下面给你几个可行的解决思路,你可以根据自己的业务场景选择:
方案1:调整Policy兼容空模型场景
既然全局作用域调用cannot('update', $model)时传入的是空模型,那我们可以在Policy的update方法里专门处理这种情况,比如从请求中获取必要的parent_relation_id来生成权限前缀:
public function update(User $user, Item $item) { // 处理全局作用域传入的空模型(无ID即未加载数据) if (is_null($item->id)) { $parentRelationId = request('parent_relation_id'); // 如果请求里也没有参数,可根据业务逻辑返回默认结果(比如拒绝) if (!$parentRelationId) { return false; } $prefix = ParentRelation::findOrFail($parentRelationId)->roles_permission_prefix; return $user->hasAnyPermission(['edit-things', "{$prefix}-edit-item"]); } // 正常处理已加载数据的模型 $prefix = $item->parentRelation->roles_permission_prefix; return $user->hasAnyPermission(['edit-things', "{$prefix}-edit-item"]); }
注意:如果你的查询场景中不一定存在parent_relation_id请求参数(比如直接调用Item::all()),这种方式可能会失效,需要结合其他逻辑补充。
方案2:在全局作用域中直接实现权限查询逻辑
既然Policy依赖模型关联的逻辑无法在全局作用域中直接复用,我们可以把权限检查的逻辑移到作用域里,通过用户已有的权限反推允许访问的Item范围:
public function apply(Builder $builder, Model $model) { $user = getUser(); if ($user === null) { $builder->where('active', '=', 1); return; } // 先检查通用权限,有则直接放行 if ($user->hasPermissionTo('edit-things')) { return; } // 提取用户所有带"-edit-item"后缀的权限,解析出前缀 $editPermissions = $user->permissions->filter(function ($perm) { return str_ends_with($perm->name, '-edit-item'); }); if ($editPermissions->isEmpty()) { // 无任何编辑权限,只显示active状态的Item $builder->where('active', '=', 1); return; } // 用前缀筛选对应的ParentRelation关联的Item $prefixes = $editPermissions->map(fn($perm) => str_replace('-edit-item', '', $perm->name)); $builder->whereHas('parentRelation', function ($query) use ($prefixes) { $query->whereIn('roles_permission_prefix', $prefixes); }); }
这种方式完全不依赖空模型的关联,直接通过用户权限构建查询条件,确保返回的都是用户有权限编辑的Item。
方案3:改用本地作用域灵活控制权限
全局作用域是自动应用到所有查询的,灵活性较差。如果你的权限控制不需要全局生效,可以改用本地作用域,在调用时传入必要的参数(比如parent_relation_id):
首先在Item模型中定义本地作用域:
public function scopeForUserEdit(Builder $builder, User $user, ?int $parentRelationId = null) { // 通用权限直接放行 if ($user->hasPermissionTo('edit-things')) { return $builder; } // 获取前缀:优先用传入的ID,没有则从请求取 $targetId = $parentRelationId ?? request('parent_relation_id'); if (!$targetId) { return $builder->where('active', '=', 1); } $prefix = ParentRelation::findOrFail($targetId)->roles_permission_prefix; // 无对应权限则只显示active的Item if (!$user->hasPermissionTo("{$prefix}-edit-item")) { $builder->where('active', '=', 1); } return $builder; }
然后在控制器中调用:
$items = Item::forUserEdit(auth()->user(), $parentRelationId)->get();
这种方式更灵活,能明确控制权限检查的时机和传入的参数,避免空模型带来的问题。
内容的提问来源于stack exchange,提问作者Fireynis
相关产品推荐
相关产品推荐

