使用 FormRequest 类封装验证最稳妥可维护,因其集中规则、便于复用、支持预处理、授权与动态规则,避免控制器臃肿及验证逻辑散落。

直接用 FormRequest 类封装是最稳妥、最可维护的方式,而不是在控制器里反复写 $request->validate() 或手动调用 Validator::make()。
为什么要用 FormRequest 而不是控制器里 validate()
因为规则会散落、复用困难、IDE 不好跳转、错误消息难统一,而且一旦涉及预处理(比如把空字符串转 null)、授权判断、或动态规则(如“不能等于当前用户字段”),控制器就迅速变得臃肿且不可测。
- 同一个邮箱校验逻辑,在注册、编辑、重置密码三个接口里各写一遍,改个正则就得找三处
-
$request->validate()在 API 场景下默认返回 HTML 重定向,不加处理会崩前端 - 想在验证前 trim 字段、过滤空格、标准化时间格式?控制器里做容易漏,
FormRequest的prepareForValidation()就是干这个的 - 想对某个字段做“存在性校验但只查库一次”,放在
rules()里会导致验证失败时也执行无谓查询——必须挪到withValidator()或自定义Rule类里
rules() 方法里怎么写数组和嵌套字段
数组字段(如 tags)和嵌套结构(如 address.city)必须拆开写,Laravel 不会自动递归解析点号路径或星号通配符以外的嵌套。
- 校验
tags是数组且至少 2 个元素:'tags' => 'required|array|min:2' - 校验每个
tags元素是字符串且不重复:'tags.*' => 'required|string|distinct|min:1' - 校验嵌套 JSON 字段
metadata.location.lat是数字且在范围内:'metadata.location.lat' => 'required|numeric|between:-90,90'(前提是请求体是application/json,且该字段能被 Laravel 自动解包) - 不要写
'metadata' => 'required|array'就完事——它只校验顶层结构,不保内层字段合法
prepareForValidation() 和 withValidator() 到底该放什么逻辑
这是最容易混淆的两个钩子:一个管“数据预处理”,一个管“验证后干预”。放错地方会导致验证失效、重复查库、或错误信息丢失。
-
prepareForValidation():只做数据清洗,比如$this->merge(['published_at' => $this->published_at ?: null]),或批量trim()字符串字段。不能查数据库,也不能抛异常 -
withValidator():验证器实例化后、执行校验前调用,适合加动态规则(如$validator->sometimes('reason', 'required|string', function ($input) { return $input->status === 'rejected'; })),或绑定自定义错误消息 - 别在
rules()里写exists:users,email这类需查库的规则——验证失败时仍会执行一次查询。应改用Rule::exists('users', 'email')并配合withValidator()做条件加载
API 场景下必须处理的三个细节
FormRequest 默认为 Web 表单设计,API 请求不处理这三点,轻则返回 302 重定向,重则暴露敏感字段或格式错乱。
- 确保
authorize()返回true或明确逻辑(如return auth()->check();),否则所有请求被静默拦截 - 在
failedValidation()方法里强制返回 JSON:throw new HttpResponseException(response()->json(['message' => 'Validation failed', 'errors' => $validator->errors()], 422)); - 如果请求体含 XML 或特殊格式字段(如
payload),得先注册自定义规则(如valid_xml),并在rules()中显式使用:'payload' => 'required|valid_xml|max:10240';注意simplexml_load_string()前必须trim(),否则空白字符导致解析失败
真正麻烦的从来不是写几条规则,而是当 tags.* 和 metadata.location.* 同时存在时,如何让错误消息精准定位到第几个数组项、哪一层嵌套字段——这需要你仔细检查 $validator->errors()->get('tags.0') 的输出结构,而不是依赖默认提示。


















