自定义验证规则必须用php artisan make:rule生成rule类,闭包规则需在规则数组中独立使用并显式调用$fail(),全局规则须在appserviceprovider的boot()中注册,跨字段验证应优先用withvalidator()。

自定义验证规则不是“要不要写”,而是“必须选对方式”——写错位置、用错语法、漏掉实例化,都会让规则静默失效或直接报错。
Rule 类怎么生成和使用才不报错
必须用 php artisan make:rule 命令生成,不能手建文件或改命名空间。生成后类默认在 app/Rules 目录,需手动 use 才能在控制器或 Form Request 中调用。
-
passes()方法必须返回布尔值,false表示失败;别忘了加return -
message()返回字符串,支持:attribute占位符,但不会自动翻译字段名——想显示“用户名”而非 “name”,得在 Form Request 的attributes()或语言文件里配映射 - 调用时必须用
new ValidPhoneNumber,不能写成'phone' => 'required|valid_phone_number'(那是全局规则的写法) - 如果规则需要参数(比如最小长度),在构造函数中接收,并在
passes()里用$this->min访问
闭包规则在哪写、怎么传参才安全
闭包只适合单次、轻量、不跨上下文的逻辑,比如拦截某个测试域名邮箱,或者做 JSON 字段结构校验。它不能访问其他字段值,也不支持参数化复用。
- 必须放在规则数组里,作为独立元素:
['email' => ['required', 'email', function ($attribute, $value, $fail) { ... }]] - 闭包必须接收三个参数:
$attribute(字段名)、$value(当前值)、$fail(错误回调) - 校验失败时,必须显式调用
$fail('提示文字'),不调就不会报错,规则等于没生效 - 别在闭包里查数据库或调外部 API——它在每次验证时同步执行,会拖慢响应,且无法被缓存或批量优化
全局规则注册为什么总失败
90% 的失败是因为注册时机错了:把 Validator::extend() 放在 register() 方法里,或放在非服务提供者的任意地方,都会触发 Call to a member function extend() on null。
- 必须在
app/Providers/AppServiceProvider.php的boot()方法内注册 - 注册后,规则名才能以字符串形式出现在管道语法里,例如:
'phone' => 'required|phone_zh' - 错误消息不能只靠闭包返回,还得在
resources/lang/zh_CN/validation.php的custom数组里补一句:'phone_zh' => ':attribute 格式不正确' - 全局规则不支持动态参数(如
phone_zh:138这种写法需额外写replacer),有参数需求优先选 Rule 类
跨字段验证该用 Rule 还是 withValidator
Rule 类本身不适合跨字段逻辑——它只拿到单个字段名和值,强行通过 $this->validator?->getData() 访问全量请求数据,既不稳定又难测试。真正需要比对两个字段(比如密码确认、起止时间)时,withValidator() 是更可控的选择。
- 在 Form Request 类里重写
withValidator($validator)方法,在里面调$validator->after()添加自定义检查 - 此时可安全读取整个请求数据:
$validator->getData()['password']和$validator->getData()['password_confirmation'] - 出错时调
$fail('密码不一致'),它会自动归到password_confirmation字段下,前端渲染不出错 - Rule 类更适合“字段自身语义校验”,比如手机号格式、用户名敏感词;跨字段依赖交给
withValidator更清晰
最常被忽略的一点:Rule 类的 passes() 方法里,$value 可能是 null 或空字符串,而内置规则(如 required)已先处理过空值。如果你的规则没前置判断,可能在空值时抛异常或返回意外结果——务必在逻辑开头加 if (is_null($value) || $value === '') return true; 或按需处理。











