Ability 是 Laravel Gate 中可复用、带上下文的布尔判断逻辑,需显式定义和调用,参数为($user, $resource=null),命名须精准表达动作+对象+粒度,不自动关联模型或角色。

Ability 是 Laravel Gate 的核心抽象,不是“功能”也不是“权限字符串”,而是一组可复用、可命名、带上下文的布尔判断逻辑。它必须显式定义、显式调用,且不自动关联模型或角色。
Gate::define 定义 ability 时,参数顺序和类型不能错
回调函数签名固定为 function ($user, $resource = null),其中 $user 是当前认证用户实例(非 ID),$resource 是可选的模型实例(如 Post、Comment)。
- 如果能力不依赖具体资源(例如 'access-dashboard'),
$resource可省略,但回调仍需只接收$user - 若传入
$post却在回调里写$post->id而没做空值检查,当路由未解析出模型时会抛Call to a member function id on null - 不要在回调里调用
$user->can()或嵌套其他 Gate 检查——这会造成循环依赖或语义混乱
ability 名称不是随便起的字符串,要能表达动作+对象+粒度
名称是 Gate 查找逻辑的唯一键,也是中间件、Blade、控制器中调用的依据。命名模糊会导致后期无法审计、难以拆分、合并后不可逆。
- ✅ 推荐:
'update-post'、'delete-comment'、'view-user-profile' - ❌ 避免:
'manage'、'admin'、'content'—— 这些无法区分动作,也不能被can:xxx中间件正确绑定 - 别用驼峰或下划线混用:
'editPost'和'edit_post'是两个不同 ability,Laravel 不做标准化转换
在 API 控制器里手动调用 Gate::allows() 时,resource 实例必须真实存在
API 请求常从 URL、Query 或 JSON Body 提取 ID,再查库获取模型。直接把 ID 传给 Gate::allows('delete-post', $id) 是无效的——Gate 回调收到的是整数,不是模型实例,$post->author_id 会报错。
- 正确做法:先查模型,再传实例
$post = Post::findOrFail($request->route('id'));if (! Gate::allows('delete-post', $post)) { abort(403); } - 如果模型可能不存在(如软删除场景),用
Post::find()+ 空值判断,避免findOrFail提前中断流程 - 别在 Gate 回调里做 DB 查询(如
Post::find($post->id))——这违反单一职责,也影响性能和测试隔离性
Ability 和 Policy 不是互斥关系,但分工必须清晰
Ability 适合跨模型、无状态、轻量级判断(如 'create-post'、'access-analytics');Policy 更适合绑定到具体模型、含复杂业务逻辑、需要多个方法组织的场景(如 PostPolicy@update()、PostPolicy@delete())。
- 不要在一个
Gate::define里复制 Policy 方法的全部逻辑,比如把PostPolicy@update的所有条件搬进Gate::define('update-post') - Policy 方法内部可以复用已定义的 ability:
return Gate::forUser($user)->check('bypass-post-limit');,但反过来不行——ability 回调不能调用$user->policy(Post::class)->update() - Blade 中
@can('update-post', $post)底层走的就是 Gate,不是 Policy;只有$this->authorize('update', $post)才触发 Policy 自动发现
真正容易被忽略的点是:ability 的生命周期完全由你控制——它不注册就不存在,不调用就不执行,不传对参数就静默失败。没有“默认开启”或“自动 fallback”,也没有隐式模型绑定兜底。写错一个参数名、漏查一次模型、多包一层 null 检查,都可能导致权限逻辑彻底失效,而且毫无报错提示。











