自定义验证规则应放在 app/rules 目录下,实现 rule 接口并定义 passes() 和 message() 方法;构造函数仅接收简单配置参数,业务逻辑和服务调用需延迟至 passes() 中通过 resolve() 或 app() 获取。

自定义验证规则该放在哪?别塞进控制器
直接在控制器里写 Validator::make() + 闭包规则,短期快,长期难维护。Laravel 的规则复用核心是「类即规则」——必须做成独立的、可被容器解析的类,否则没法在 Request 类、API 路由、队列任务里统一调用。
- 正确位置:放在
app/Rules目录下(需手动创建),类名如UppercaseFirstLetter - 必须实现
Illuminate\Contracts\Validation\Rule接口,至少有passes()和message()两个方法 - 不要用
php artisan make:rule生成的模板去改逻辑——它默认带__invoke,但那是为闭包式调用准备的,和 Rule 接口不兼容,混用会导致TypeError: Rule must implement Rule interface
Rule 类里怎么访问请求数据或服务?不能硬 new
Rule 类是无状态的,Laravel 每次验证都会新建实例,所以不能在构造函数里依赖注入复杂对象(比如 UserService),否则容器无法解析;但你可以把依赖“延迟”到 passes() 里通过 Laravel 的服务容器获取。
- 构造函数只接收简单参数(如最小长度、允许的前缀),用于配置规则行为
- 需要 DB 查询或业务逻辑时,在
passes()内用app(UserRepository::class)或resolve(UserRepository::class)获取实例 - 避免在
message()里做任何逻辑计算——它只负责返回字符串,否则可能因多次调用引发副作用 - 示例:
passes()中写if (app(EmailBlacklist::class)->isBlocked($value)) { ... }是安全的
在 FormRequest 和普通验证中怎么调用?写法不一样
同一个 Rule 类,在不同场景下传参和注册方式有差异,错一个地方就报 Class not found 或静默失效。
- 在
FormRequest的rules()方法里:直接 new 实例,如'email' => [new EmailNotBanned()] - 在
Validator::make()中:同样 new,但注意数组键名必须匹配字段,且不能漏掉[]包裹,['email' => [new EmailNotBanned()]]才对 - 如果想用字符串形式注册(比如全局规则),得在
AppServiceProvider::boot()里调用Validator::extend('not_banned', EmailNotBanned::class),但这时类必须有静态validate()方法,和接口式 Rule 不兼容——别混用
为什么验证失败后提示总显示 default message?没走自定义
不是代码没生效,而是 Laravel 的 message 解析机制跳过了你的 message() 方法——常见于字段名含点号(user.email)、规则用了数组语法但没配好 key,或者 Rule 类没加 public function message() 的 return 声明。
- 确保
message()方法返回 string,不是 echo 或 void;类型声明写public function message(): string更保险 - 如果字段是嵌套的(如
addresses.*.zip),Rule 实例会被多次调用,每次$attribute是完整路径(如addresses.0.zip),别在 message 里硬编码字段名 - 调试时可在
passes()开头加Log::debug("validating $attribute = $value"),确认是否真被触发 - 别在
resources/lang/en/validation.php里为自定义 Rule 加 key——那只会覆盖全局翻译,不会绑定到你的类
passes() 里 resolve 来平衡。写完记得跑一次单元测试,专测验证失败时的 message 是否准确,这步漏了,线上就只能靠用户反馈才发现提示不对。











