Laravel表单请求类(FormRequest)实战指南:5个场景告别控制器臃肿
目录导读
- 为什么需要FormRequest?——控制器“瘦身”的必然选择
- 基础验证——从
$request->validate()到类的迁移 - 授权逻辑(Authorization)——把
Gate关进“请求”里 - 复杂条件验证——依赖注入与
withValidator()钩子 - 表单数据预处理——
prepareForValidation()的魔法 - 错误响应定制——前后端分离下的JSON格式输出
- 高频问答(FAQ)与最佳实践陷阱
为什么需要FormRequest?——控制器“瘦身”的必然选择
很多PHP开发者初学Laravel时,习惯将验证逻辑写在控制器方法内,

public function store(Request $request) {
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
// 业务代码...
}
当项目只有3-5个方法时,这种方式尚可,但一旦涉及用户资料更新、订单提交、API资源创建等复杂场景,控制器会迅速膨胀至上千行,且验证规则无法复用。Laravel表单请求类(FormRequest)正是为解决此痛点而生——它将验证、授权、数据预处理封装为独立类,遵循“单一职责原则”,让控制器只关注业务逻辑。
场景一:基础验证——从$request->validate()到类的迁移
典型场景:创建一个文章内容管理系统(CMS)的存储接口。
实现步骤:
// 1. 生成请求类
php artisan make:request StoreArticleRequest
// 2. 定义规则
class StoreArticleRequest extends FormRequest
{
public function authorize() { return true; }
public function rules()
{
return [
'title' => 'required|string|max:255|unique:articles,title',
'content' => 'required|string|min:20',
'category_id' => 'required|exists:categories,id',
];
}
}
// 3. 控制器注入使用
public function store(StoreArticleRequest $request) {
Article::create($request->validated());
// 业务逻辑...
}
对比优势:规则内聚、可单元测试、且validated()方法直接返回过滤后的安全数据。
场景二:授权逻辑(Authorization)——把Gate关进“请求”里
痛点:如果只有文章作者或管理员能更新文章,在控制器里写if(auth()->user()->id !== $article->user_id)会让逻辑分散。
解法:在authorize()方法中定义权限:
class UpdateArticleRequest extends FormRequest
{
public function authorize()
{
$article = $this->route('article');
// 方法一:直接比对
return $this->user()->can('update', $article);
// 方法二:使用Gate门面
// return Gate::allows('update-article', $article);
}
public function rules() { /* ... */ }
}
注意:若授权失败,Laravel自动返回403响应,这在构建管理后台时,能有效避免每个控制器方法重复写abort_unless()。
场景三:复杂条件验证——依赖注入与withValidator()钩子
难题:假设促销活动表单中,要求“活动结束时间必须晚于开始时间”,这无法用原生required规则表达。
解决方案:通过构造函数注入服务,并在withValidator()中追加规则:
class StorePromotionRequest extends FormRequest
{
protected $promotionService;
public function __construct(PromotionService $service) {
parent::__construct();
$this->promotionService = $service;
}
public function withValidator($validator)
{
$validator->after(function ($validator) {
$start = $this->input('start_date');
$end = $this->input('end_date');
if ($end <= $start) {
$validator->errors()->add('end_date', '结束日期必须大于开始日期。');
}
// 可调用服务做业务校验(如库存检查)
if (!$this->promotionService->checkStock($this->input('product_id'))) {
$validator->errors()->add('product_id', '该商品库存不足。');
}
});
}
}
优势:保持验证器纯净,同时能处理跨字段依赖和外部服务校验。
场景四:表单数据预处理——prepareForValidation()的魔法
需求:前端提交的“手机号”可能带有空格或连字符,或需要将复选框的“'on'”转成布尔值,在验证前清洗数据,能避免规则误判。
class UpdateProfileRequest extends FormRequest
{
protected function prepareForValidation()
{
$this->merge([
'phone' => preg_replace('/[\s\-]/', '', $this->input('phone')),
'is_subscribed' => $this->has('newsletter') ? true : false,
]);
}
public function rules()
{
return [
'phone' => 'required|regex:/^1[3-9]\d{9}$/',
'is_subscribed' => 'boolean',
];
}
}
深层价值:统一处理输入规范,避免在业务层写一堆str_replace。
场景五:错误响应定制——前后端分离下的JSON格式输出
痛点:纯API项目(或Ajax请求)默认返回422状态码及错误数组,但前端需要特定结构。
定制方案:重写failedValidation()方法。
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class ApiBaseRequest extends FormRequest
{
protected function failedValidation(Validator $validator)
{
$response = response()->json([
'success' => false,
'message' => '数据验证失败',
'errors' => $validator->errors()->toArray(),
], 422);
throw new HttpResponseException($response);
}
}
效果:所有API请求类继承该基类,即可统一错误格式,无需在每个控制器写try-catch。
高频问答(FAQ)与最佳实践陷阱
Q1: authorize()返回false时,会返回JSON响应吗?
- A: 默认跳转至或返回403页面,若需JSON,可重写
failedAuthorization()方法。
Q2: 当请求类是构造器依赖注入时,authorize()可以调用模型吗?
- A: 可以,但必须通过
$this->route('parameter_name')获取路由模型绑定对象。
Q3: 如何在FormRequest中使用“有时验证规则”(sometimes)?
- A: 直接在
rules()内使用Rule::when()或sometimes方法,如:Validator::sometimes(),但FormRequest中可直接写$validator->sometimes(...)在withValidator中。
最佳实践陷阱:
- 不要在
rules()中写unique时忽略当前ID,应使用Rule::unique('table')->ignore($this->article) - 慎用
$request->all()在控制器中,务必只使用$request->validated()。 - 性能:每个表单请求类独立文件,不会造成加载负担,但需定期重构重复规则至
Rule对象。
Laravel表单请求类不是“复杂概念”,而是工程化必备工具,从控制器的“水管工”角色中解放出来,让验证与授权成为高内聚的“独立零件”,掌握上述5个场景后,你的代码将拥有更强的可读性、可测试性,并为后续维护节省大量时间,立即动手,重构你的下一个store方法吧!