推荐用 php artisan make:rule uppercase 创建可复用 rule 类,passes() 必须明确返回 true 或 false,不可抛异常或写验证逻辑到 message();数据库查询宜前置缓存,避免事务干扰;不支持依赖注入,禁用 validator::make() 和 auth()->user()。

怎么写一个自定义 Rule 类(Laravel 9+ 推荐方式)
直接用 php artisan make:rule Uppercase 生成类,比手动写闭包或在 Request 类里塞逻辑更清晰、可复用。这个命令生成的 Uppercase 类默认带 passes() 和 message(),你只需要填逻辑。
常见错误是把验证逻辑写进 message() 里,或者忘了返回布尔值——passes() 必须明确 return true 或 false,否则验证永远通过。
-
passes()里别 throw 异常,Laravel 验证器不捕获它,会导致 500 - 如果要访问当前请求数据,用
$this->data(Laravel 10+)或通过构造函数传入Request实例 - 多语言提示别硬编码,用
__('validation.uppercase'),然后在lang/en/validation.php里加对应键
Rule::when() 和 Rule::requiredIf() 什么时候该用闭包而不是自定义 Rule 类
当验证逻辑只在某几个字段间联动、且不跨请求复用时,闭包更轻量。比如「只有勾选了 is_premium 才校验 expiry_date」,用 Rule::requiredIf() 比另建一个 Rule 类合理得多。
容易踩的坑是误以为 Rule::when() 能替代整个规则链——它只是条件开关,里面还得塞真正的规则,比如 Rule::when($request->is_premium, ['required', 'date']),漏掉数组会报错 Array to string conversion。
-
Rule::requiredIf()的回调参数是Request $request,不是原始数组,别直接取$_POST - 闭包里不能访问
$this,想复用逻辑得提成独立函数或静态方法 - Laravel 9.28+ 开始,
Rule::when()支持传字符串条件,但推荐用闭包,语义更稳
validateWithBag() 和自定义 Rule 冲突吗
不冲突,但要注意错误包(error bag)只影响前端渲染位置,不影响 Rule 类内部行为。你写了一个 UniqueEmail Rule,它抛出的错误默认进 default bag;调用 $request->validateWithBag('login') 只是把所有错误都挪到 login 这个 bag 里,Rule 本身完全无感。
真正容易出问题的是:你在 Rule 里手动调用了 Validator::make() 或改了 $validator->errors(),这会绕过 Laravel 的 bag 管理机制,导致错误不进指定 bag。
- Rule 类里禁止 new Validator 或调用
Validator::make() - 想动态加错误?用
$validator->errors()->add('field', 'message'),但仅限在FormRequest的withValidator()里做 - 自定义 Rule 的
message()返回值仍受lang/xx/validation.php的custom键控制,和 bag 无关
Rule 类里怎么安全地查数据库(避免 N+1 或事务干扰)
Rule 类本身没生命周期钩子,passes() 被调用时,Eloquent 已初始化但事务可能未开启。如果你在 Rule 里写 User::where('email', $value)->exists(),它走的是当前连接,和主事务一致——但若验证失败后回滚,这个查询结果其实已不可信。
更稳妥的做法是:把 DB 查询提到 FormRequest 的 prepareForValidation() 里缓存结果,再传给 Rule;或者用 DB::table() + selectRaw('1') 减少数据传输。
- 别在 Rule 里用
auth()->user(),因为验证发生在中间件之后、控制器之前,session 可能未完全加载 - 涉及软删除模型,记得加
->withoutTrashed(),否则unique规则可能误判已删除记录 - Rule 类不支持依赖注入(如
UserRepository),要用app()手动解析,但尽量避免
最常被忽略的一点:Rule 类的 passes() 方法里,$attribute 是字段名(如 email),$value 是原始输入值——它不会自动经过 cast 或 accessor 处理。如果你的字段是 JSON 字段或带 mutator,得自己 decode 或调用对应方法,不然对比永远失败。









