验证规则写在控制器里,起初只有三条,半年后变成三十条,还夹着权限判断与数据准备。把验证搬进 FormRequest,控制器就能回到「只处理业务」的状态。

用 FormRequest 收拢验证
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()?->can('create', Post::class) ?? false;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:80', 'unique:posts,title'],
'slug' => ['nullable', 'string', 'max:120', 'alpha_dash'],
'category_id' => ['required', 'integer', 'exists:categories,id'],
'content' => ['required', 'string', 'min:20'],
'tags' => ['array'],
'tags.*' => ['string', 'max:20'],
];
}
}
控制器里只需要类型提示,Laravel 会在进入方法前完成验证与授权:
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
return redirect()->route('posts.show', $post);
}
validated() 只返回规则里声明过的字段,这一点很重要:它能防止用户多提交 status、views 这类不该由前端决定的字段。
自定义规则对象
复杂逻辑(例如「日期必须在另一个字段之后」或「编码必须符合内部规范」)写闭包会越来越乱。用规则类可以复用、可以单元测试。
class ValidPostSlug implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (preg_match('/^\d+$/', $value)) {
$fail('网址别名不能是纯数字,否则会和文章 ID 冲突。');
return;
}
if (in_array($value, ['admin', 'api', 'login'], true)) {
$fail('这个网址别名是系统保留词,请换一个。');
}
}
}
然后在规则数组里写 new ValidPostSlug 即可。规则类的好处是错误信息与判断逻辑放在一起,不会散落在控制器里。
字段名与错误信息本地化
默认错误提示里出现的是英文字段名,用户看不懂。用 attributes() 换成中文:
public function attributes(): array
{
return [
'title' => '标题',
'category_id' => '分类',
'content' => '正文',
];
}
需要整站统一时,把映射放到 lang/zh_CN/validation.php 的 attributes 里,不用每个 FormRequest 重复写。
页面与 API 的错误格式
同一个 FormRequest 同时服务页面与接口时,错误响应格式应该分开。Laravel 默认会根据请求头判断,但自定义格式更可控:
protected function failedValidation(Validator $validator): void
{
if ($this->expectsJson()) {
throw new HttpResponseException(response()->json([
'message' => '参数校验未通过',
'errors' => $validator->errors(),
], 422));
}
parent::failedValidation($validator);
}
这样前端拿到的永远是 { message, errors } 结构,页面则继续走「回跳 + 闪存错误」的默认流程。
前后端规则保持一致
- 前端只做「即时反馈」,后端规则才是最终约束,不要只依赖前端;
- 把长度、格式这类规则同步到前端提示文案里,避免用户提交后才看到错误;
- 接口文档里写清必填项与取值范围,比让调用方猜要省很多沟通。
处理用户粘贴的长文本时,先用站内的文本处理工具去掉多余空行与首尾空格,能减少不少「长度超限」的误报。验证规则写好后,也建议为关键的规则类补几个单元测试。
小结
FormRequest 的价值不只是「少写几行」,而是把「什么样的输入是合法的」变成一处可读、可测、可复用的定义。规则一旦集中,排查「为什么这个请求被拒绝」就只需要看一个文件。