laravel中formrequest通过类型提示自动验证:检测到子类时拦截请求并执行authorize()和rules(),失败则返回422或重定向;需确保authorize()返回true、避免手动validate、重写failedvalidation()统一api响应格式,复用依赖di容器识别类名,动态规则应使用$this->route()等上下文方法,非http入口场景不宜使用。

控制器方法直接类型提示 FormRequest 类
这是最核心的配合方式:Laravel 在控制器方法参数中检测到 FormRequest 子类时,会自动拦截请求、执行 authorize() 和 rules(),验证失败就终止流程并返回 422(API)或重定向(Web)。不需要手动调用 $request->validate(),也不用写 if 判断。
常见错误现象:
• 写了 StoreUserRequest $request 却没生效,仍能提交非法数据 → 检查是否漏写了 public function authorize(): bool { return true; }(默认是 false,直接拒掉所有请求)
• 控制器里还保留 $this->validate($request, [...]) → 冗余且可能干扰自动流程,删掉
- 必须继承
Illuminate\Foundation\Http\FormRequest -
authorize()返回false时触发 403 响应,不是 422 - 验证通过后,
$request->all()、$request->validated()等方法才安全可用
验证失败时如何控制响应格式
默认行为对 API 不友好:Web 请求重定向回表单页,API 请求返回原始 errors 数组(无 success、message 等字段)。要统一 JSON 结构,必须重写 failedValidation() 方法。
使用场景:
• 前端要求固定字段名(如 errors 必须是对象,不能是数组)
• 需要补充业务提示(如 “注册失败,请检查邮箱”)而非仅字段级错误
- 在
FormRequest类中覆盖protected function failedValidation(Validator $validator) - 必须抛出
HttpResponseException,不能只 return response() - 示例:
protected function failedValidation(Validator $validator) { throw new HttpResponseException(response()->json([ 'success' => false, 'message' => '数据验证失败', 'errors' => $validator->errors(), ], 422)); }
同一个 FormRequest 被多个控制器方法复用的条件
复用不是靠“名字一样”,而是靠 Laravel 的依赖注入容器识别类名。只要控制器方法参数类型声明为同一个 FormRequest 子类,框架就复用同一套规则和逻辑。
容易踩的坑:
• 复用时发现邮箱唯一性校验失效(比如更新用户还要排除自己)→ 不能硬编码规则,得在 rules() 里动态取 ID:'email' => 'required|email|unique:users,email,' . $this->user->id
• 在 authorize() 里用了 $this->route('id'),但某个控制器调用时没传路由参数 → 抛出异常,应加空值判断
- 复用的前提是规则和授权逻辑本身不强耦合具体控制器上下文
- 若需差异化(如创建 vs 更新),优先用继承(
UpdateUserRequest extends StoreUserRequest),而不是在同一个类里 if-else 分支 -
$this->input('xxx')和$this->route('xxx')在rules()和authorize()中都可用,但注意生命周期:此时请求已解析,但控制器方法尚未执行
什么时候不该用 FormRequest?
不是所有验证都适合抽成 FormRequest。它本质是「有明确 HTTP 入口 + 需要统一拦截」的场景专用机制。
典型反例:
• Service 层内部调用的数据校验(比如订单创建时校验库存)→ 用独立的 Validator 或自定义 Rule 类更合适
• 同一接口部分字段需动态开关(如“开启短信通知”才校验手机号)→ FormRequest 规则写死,不如在控制器里用 Validator::make() 拼规则数组
-
FormRequest强绑定 HTTP 生命周期,无法脱离请求上下文使用 - 过度拆分(每个小表单都建一个 Request 类)会导致
app/Http/Requests目录爆炸,反而难维护 - 调试时注意:
dd()放在rules()里会中断整个验证流程,看不到后续控制器逻辑 —— 这是设计使然,不是 bug
authorize() 和 rules() 是在控制器执行前运行的,但它们能访问的只有当前请求上下文($this->input()、$this->route()、$this->user()),没法直接调用 service 或 query builder。需要动态数据时,得提前在 request 构造阶段准备,或者接受它本就不该承担太重的业务逻辑。











