PHP项目Laravel表单请求类使用场景

wen PHP项目 2

Laravel表单请求类(FormRequest)实战指南:5个场景告别控制器臃肿


目录导读

  1. 为什么需要FormRequest?——控制器“瘦身”的必然选择
  2. 基础验证——从$request->validate()到类的迁移
  3. 授权逻辑(Authorization)——把Gate关进“请求”里
  4. 复杂条件验证——依赖注入与withValidator()钩子
  5. 表单数据预处理——prepareForValidation()的魔法
  6. 错误响应定制——前后端分离下的JSON格式输出
  7. 高频问答(FAQ)与最佳实践陷阱

为什么需要FormRequest?——控制器“瘦身”的必然选择

很多PHP开发者初学Laravel时,习惯将验证逻辑写在控制器方法内,

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中。

最佳实践陷阱

  1. 不要rules()中写unique时忽略当前ID,应使用Rule::unique('table')->ignore($this->article)
  2. 慎用$request->all()在控制器中,务必只使用$request->validated()
  3. 性能:每个表单请求类独立文件,不会造成加载负担,但需定期重构重复规则至Rule对象。

Laravel表单请求类不是“复杂概念”,而是工程化必备工具,从控制器的“水管工”角色中解放出来,让验证与授权成为高内聚的“独立零件”,掌握上述5个场景后,你的代码将拥有更强的可读性、可测试性,并为后续维护节省大量时间,立即动手,重构你的下一个store方法吧!

抱歉,评论功能暂时关闭!