用户注册、文章发布、订单提交和文件上传等功能,都需要在服务端检查请求参数。如果把所有验证规则都写在控制器中,代码很快就会变得臃肿。Laravel 提供的 Form Request 可以把参数验证和授权逻辑放进独立请求类,让控制器只负责处理已经通过验证的数据。
一、什么是Laravel Form Request
Form Request 是 Laravel 中专门用于封装请求验证和授权逻辑的自定义请求类。它继承自 Laravel 的基础请求对象,可以定义验证规则、错误信息、字段名称和请求授权条件。
与直接在控制器中调用 $request->validate() 相比,Form Request 更适合以下场景:
- 表单字段较多;
- 验证规则需要重复使用;
- 控制器需要保持简洁;
- 接口需要统一返回验证错误;
- 请求提交前需要先判断操作权限。
如果只是验证一两个简单字段,直接使用 $request->validate() 也没有问题。随着业务复杂度提高,再把验证逻辑拆到 Form Request 中会更容易维护。
二、创建Form Request请求类
在 Laravel 项目根目录执行以下命令:
php artisan make:request StorePostRequest
执行后,Laravel 会在 app/Http/Requests 目录下创建 StorePostRequest.php 文件。
基础结构如下:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:200'],
'content' => ['required', 'string'],
'status' => ['required', 'integer', 'in:0,1'],
];
}
}
authorize() 用于判断当前用户是否有权执行这个请求,rules() 用于定义字段验证规则。
如果 authorize() 返回 false,Laravel 会拒绝请求;如果暂时不需要权限判断,应明确返回 true。
三、在控制器中使用Form Request
创建请求类后,在控制器方法中注入 StorePostRequest:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
class PostController extends Controller
{
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
return redirect()
->route('posts.show', $post)
->with('success', '文章创建成功');
}
}
Laravel 会在进入 store() 方法前自动执行验证。验证失败时,传统表单请求通常会返回上一页并携带错误信息;对于预期返回 JSON 的请求,Laravel 会返回包含验证错误的 JSON 响应。
验证通过后,可以通过以下方法取得有效数据:
$data = $request->validated();
也可以通过 safe() 获取经过验证的数据容器:
$data = $request->safe()->only([
'title',
'content',
'status',
]);
这样可以避免把未经验证的额外字段直接写入数据库。
四、常用Laravel验证规则
Laravel 提供了较完整的验证规则,日常项目中常用的包括:
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100'],
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'age' => ['nullable', 'integer', 'min:18'],
'website' => ['nullable', 'url'],
'status' => ['required', 'in:draft,published'],
'published_at' => ['nullable', 'date'],
'category_id' => ['required', 'exists:categories,id'],
];
}
这些规则分别完成必填、字符串长度、邮箱格式、唯一性、密码确认、数字范围、网址格式、指定值范围、日期格式和关联记录存在性检查。
数组写法比使用竖线拼接规则更清晰,尤其适合规则中包含对象或复杂条件的情况。
五、自定义错误提示
默认错误提示可以满足基础使用,但中文项目通常需要根据页面场景自定义文案。
在 Form Request 中增加 messages() 方法:
public function messages(): array
{
return [
'title.required' => '请输入文章标题',
'title.max' => '文章标题不能超过200个字符',
'content.required' => '请输入文章正文',
'status.in' => '文章状态参数不正确',
];
}
数组键由“字段名.规则名”组成。例如,title.required 表示 title 字段没有通过 required 规则。
还可以通过 attributes() 定义更加友好的字段名称:
public function attributes(): array
{
return [
'title' => '文章标题',
'content' => '文章正文',
'status' => '文章状态',
];
}
定义后,Laravel 在生成错误信息时会使用这些字段名称。
六、表单页面怎么显示验证错误
Blade 模板可以使用 $errors 变量显示错误信息:
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
如果只显示指定字段的错误,可以使用 @error:
<input
type="text"
name="title"
value="{{ old('title') }}"
>
@error('title')
<div class="text-danger">{{ $message }}</div>
@enderror
old('title') 可以恢复用户上一次提交的内容,避免验证失败后重新填写整个表单。
七、编辑数据时处理唯一性验证
创建用户时,可以直接验证邮箱唯一:
'email' => ['required', 'email', 'unique:users,email'],
编辑用户时,需要忽略当前记录,否则原邮箱也会被判断为重复。可以使用 Rule::unique()->ignore():
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
'email' => [
'required',
'email',
Rule::unique('users', 'email')->ignore($this->user),
],
];
}
传给 ignore() 的值应来自可信的模型实例或系统生成的 ID,不能直接使用用户可控的请求参数,避免引入 SQL 注入风险。
八、根据请求条件动态验证
部分字段是否必填,可能取决于另一个字段。例如,文章状态为已发布时,必须填写发布时间:
public function rules(): array
{
return [
'status' => ['required', 'in:draft,published'],
'published_at' => [
'nullable',
'date',
'required_if:status,published',
],
];
}
如果规则需要更复杂的判断,可以使用 withValidator() 或 after() 添加验证后的检查逻辑。
示例:
use Illuminate\Validation\Validator;
public function after(): array
{
return [
function (Validator $validator) {
if (
$this->input('status') === 'published'
&& empty($this->input('content'))
) {
$validator->errors()->add(
'content',
'发布文章前必须填写正文'
);
}
},
];
}
这类写法适合跨字段判断,但简单条件优先使用 Laravel 已有验证规则,代码会更容易理解。
九、验证前整理请求数据
如果前端提交的数据需要先清理,可以在 prepareForValidation() 中处理:
protected function prepareForValidation(): void
{
$this->merge([
'title' => trim((string) $this->title),
'status' => $this->status ?: 'draft',
]);
}
该方法会在正式验证前执行,适合清理空格、统一格式或补充默认值。
不要在这里处理复杂业务逻辑,也不要把无法信任的输入直接转换成拥有更高权限的字段。
十、使用authorize进行请求授权
Form Request 不只负责参数验证,也可以判断当前用户是否允许执行操作。
例如,只允许文章作者更新文章:
public function authorize(): bool
{
return $this->user()?->can('update', $this->post) ?? false;
}
这里通常配合 Laravel Policy 使用。授权失败时,Laravel 会返回禁止访问响应,控制器不会继续执行。
参数验证解决的是“提交的数据是否正确”,授权解决的是“当前用户能否执行操作”。这两个概念不能混在一起。
十一、API验证失败怎么处理
当请求预期返回 JSON 时,Laravel 会返回验证错误信息,常见状态码为 422 Unprocessable Content。前端可以根据 errors 字段,将不同字段的错误显示在表单旁边。
请求示例:
{
"title": "",
"content": "测试内容",
"status": "unknown"
}
验证失败后,响应中会包含具体字段错误。前端不应该只显示“提交失败”,而应把标题不能为空、状态参数不正确等信息准确反馈给用户。
如项目需要统一接口格式,可以在异常处理层集中转换验证异常,但要保留字段级错误,方便前端定位问题。

