Laravel查询构建器中能否将Policy策略作为查询条件?
在Laravel查询构建器中直接使用策略作为过滤条件?
现有代码
$userSpacesTimelines = $user ->spaces() ->select('spaces.id', 'spaces.uuid', 'spaces.slug', 'spaces.privacy_type') ->with('timeline:id,owner_type,owner_id') ->get(); $filteredSpace = $userSpacesTimelines->filter(function ($space) { return auth()->user()->can('viewTimeline', $space); });
当前问题
通过viewTimeline策略过滤spaces时,该策略逻辑涉及多张表、百行以上代码,导致先查询全量数据再内存过滤的方式效率极低,希望能直接在查询构建器层面将策略作为条件过滤,比如实现类似$user->whereCan('viewTimeline')->spaces();的写法。
解决方案
可以实现,Laravel提供原生方式在数据库层面整合策略权限过滤,以下是两种核心方法:
1. 给模型添加权限查询作用域
把viewTimeline策略中的数据库查询逻辑提取成Space模型的本地作用域,直接在查询构建器中调用:
// Space模型中添加本地作用域 public function scopeWhereCanViewTimeline($query, $user) { // 迁移策略中的多表关联判断逻辑到这里,比如用whereExists、join等 return $query->whereExists(function ($subquery) use ($user) { // 示例:关联timeline表的权限判断 $subquery->select(DB::raw(1)) ->from('timelines') ->whereColumn('timelines.owner_id', 'spaces.id') ->where('timelines.owner_type', Space::class) // 这里补充策略中的其他权限条件 ->where('timelines.some_permission_column', $user->id); }); }
调用方式:
$filteredSpaces = $user->spaces() ->select('spaces.id', 'spaces.uuid', 'spaces.slug', 'spaces.privacy_type') ->with('timeline:id,owner_type,owner_id') ->whereCanViewTimeline(auth()->user()) ->get();
2. 从策略中提取查询约束
如果不想修改现有策略,可以通过Gate获取策略实例,手动将策略逻辑转化为查询构建器条件:
$policy = Gate::getPolicyFor(Space::class); $currentUser = auth()->user(); $filteredSpaces = $user->spaces() ->select('spaces.id', 'spaces.uuid', 'spaces.slug', 'spaces.privacy_type') ->with('timeline:id,owner_type,owner_id') ->where(function ($query) use ($policy, $currentUser) { // 直接在闭包中编写策略对应的数据库查询条件 // 或者在策略中新增方法封装查询约束,比如: // $policy->applyViewTimelineQuery($query, $currentUser); }) ->get();
注意:如果策略包含非数据库层面的逻辑(如缓存校验、外部API调用),这部分仍需在查询完成后做内存过滤。
内容的提问来源于stack exchange,提问作者Hendri Triwanto
相关产品推荐
相关产品推荐

