gate和policy是分工明确的授权工具:gate适用于无模型依赖的通用权限判断,policy专用于绑定eloquent模型的细粒度操作;二者可组合使用,gate做全局拦截,policy处理模型级逻辑。

Gate 和 Policy 不是互斥选项,而是分工明确的授权工具。选错会导致代码冗余、权限失效或调试困难——关键不在版本(9/10/11 差异极小),而在使用场景和写法规范。
Gate 适合无模型依赖的通用判断
当权限逻辑不依赖具体数据实例时,用 Gate 更轻量、易测试、易复用。
- 定义方式:在 AuthServiceProvider::boot() 中调用 Gate::define('delete-user', function ($user) { return $user->is_admin; })
- 闭包第一个参数必须是 $user,哪怕你没用到它;若传入 null 或非 User 实例,结果直接为 false,且不报错
- 调用时可不传资源:如 Gate::allows('edit-settings'),也可传多个参数(Gate::allows('create-post', [$category, $flag]))
- 适用于后台开关、角色硬编码、访客限制等场景,比如「是否允许导出报表」或「是否开启灰度功能」
Policy 专用于绑定模型的细粒度操作
只要判断涉及某个 Eloquent 模型实例(如 Post、Comment),就该用 Policy,否则容易绕过业务规则。
系统化 Debug 与根因调查框架:通过调查‑分析‑假设‑验证四步追踪根本原因,只进行根因修复,适用于 Bug 调试、异常行为分析、服务报错排查,提供可验证的结论。
- 方法名必须与 can() 调用字符串完全一致:@can('edit', $post) → 必须有 PostPolicy::edit(User $user, Post $post)
- 不能靠 IDE 补全猜名,也不能依赖自动映射(edit 不会 fallback 到 update);名字错就抛 BadMethodCallException
- 必须在 AuthServiceProvider::boot() 中显式注册:Gate::policy(Post::class, PostPolicy::class),漏掉就静默失效
- Policy 内禁止调用 request() 或 session(),所有依赖需通过参数传入
调用方式统一但语义不同
无论用 Gate 还是 Policy,Blade、控制器、中间件里都用相似语法,但底层行为差异大。
- @can('update', $post):先查对应 Policy 方法,不存在则 fallback 到同名 Gate;但 Policy 方法名错会直接报错,不会走 fallback
- $this->authorize('update', $post):只找 Policy,找不到就 403,不查 Gate
- Gate::allows('update-post', $post):只执行 Gate 定义,不进 Policy,即使已注册
- 混用时注意:控制器里写 $user->can('update', $post) 和 $this->authorize('update', $post) 行为不同,前者可能走 Gate,后者强制走 Policy
什么时候该组合用,而不是二选一
复杂权限常需分层协作,不是非此即彼。
- 先用 Gate 做全局拦截:比如 super_admin 可跳过所有检查,用 Gate::before() 统一放行
- 再用 Policy 处理模型级逻辑:如「作者可编辑自己文章」+「编辑需审核状态为草稿」
- 最后用角色包(如 spatie/laravel-permission)管理权限名集合,让 Gate 和 Policy 都基于同一套权限标识工作
- 例如定义 Gate 'publish-post',内部调用 $user->can('publish', $post),把策略判断收口到 Policy,保持职责清晰










