
验证逻辑该写在控制器里还是单独抽成 FormRequest?
绝大多数复杂表单验证应该放在 FormRequest 类里,而不是直接塞进控制器。控制器只负责调度和业务流转,验证属于“输入契约”,天然适合隔离。
-
FormRequest自动绑定到路由,请求一进来就校验,失败直接中断,控制器方法根本不会执行 - 规则、错误消息、授权判断(
authorize())全封装在一个类里,可复用、可测试、可文档化 - IDE 能自动跳转到规则定义处,团队协作时比散落在控制器里的
$this->validate()更易定位 - API 和 Web 共用同一套验证逻辑时,
FormRequest是唯一能同时适配两种场景的方案(Laravel 10+ 对 JSON 请求体也自动解包)
什么时候可以破例写在控制器里?
仅限极简、一次性、无复用需求的场景,比如后台临时调试接口或内部工具页的单字段校验。一旦出现以下任一情况,立刻移出控制器:
- 规则超过 3 条,或含
unique、exists、confirmed等需查库/跨字段依赖的规则 - 需要对不同角色返回不同错误提示(比如管理员看到「邮箱已被禁用」,普通用户只看到「邮箱不可用」)
- 后续可能被其他控制器方法复用(如
store和update共享大部分规则) - 前端是 SPA,后端只提供 API,且错误格式需严格控制(
FormRequest的failedValidation()可完全接管响应结构)
FormRequest 里最容易漏掉的三件事
生成后别急着写 rules(),先确认这三项是否已处理:
-
authorize()方法默认返回false,不改成true或加权限判断,所有请求都会被拒绝(403) - 文件验证(如
image|max:2048)必须配合multipart/form-data表单 enctype,且控制器接收时不能用$request->all()—— 文件对象会被转成空数组,得用$request->validated() - 自定义错误消息若放在
messages()方法里,键名必须严格匹配规则路径,例如'email.required',错一个字符(如写成'email.requireds')就失效
多步骤表单或条件验证怎么办?
不要硬塞进一个 FormRequest。复杂条件(如「当 type=contract 时,require signed_at 字段」)应拆解为:
- 基础规则放
rules()(保证必填字段存在) - 动态规则用
withValidator()钩子注入:public function withValidator(Validator $validator) { if ($this->type === 'contract') { $validator->sometimes('signed_at', 'required|date', function ($input) { return $input->type === 'contract'; }); } } - 跨步骤状态(如 step=2 时校验 step=1 提交的数据)建议用 session 或缓存暂存前序数据,验证逻辑仍留在对应步骤的
FormRequest中
真正麻烦的不是规则怎么写,而是忘记 FormRequest 默认不读取 request()->file() 的原始句柄 —— 它只认 request()->validated() 解析后的结构。这点在上传组件(如 Dropzone)集成时,90% 的人第一次都会卡住。











