封装 Laravel 自定义表单请求

zxr615 -
封装 Laravel 自定义表单请求
背景

Laravel 提供的自动表单验证请求类,通常一个 class 是应用到一个 Action 上的,虽说可以应用到多个 Action 上,但验证参数很少说完全一样,粒度太细了,如果一个 Controller 有 10 个 Action 那就得对应创建10个验证规则类,会导致文件太多,所以可以封装一下 Request ,把粒度由 Action 变成 Controller 级别得粒度,这样一个 Controller 就只用创建一个表单请求类了, 实现效果如下,结果都一致:

image.png

原有验证方式创建验证规则

app/Http/Requests

├── Requests
│   ├── DeleteBlog.php
│   ├── StoreBlog.php
│   └── UpdateBlog.php

为了方便展示,放在了一个文件内

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreBlogRequest extends FormRequest {
    public function rules() {
        return [
            'title'   => 'required|max:100',
            'content' => 'required|max:1000'
        ];
    }
    
    public function messages() {return []; }
}

class UpdateBlogRequest extends FormRequest {
    public function rules() {
        return [
            'id'      => 'required|integer',
            'title'   => 'required|max:100',
            'content' => 'required|max:1000'
        ];
    }
    
    public function messages() {return []; }
}

class DeleteBlogRequest extends FormRequest {
    public function rules() {
        return [
            'id' => 'required|integer',
        ];
    }
    
    public function messages() {return []; }
}
使用验证规则

app/Http/Comtrollers/PostController

namespace App\Http\Controllers;

use App\Http\Requests\DeleteBlogRequest;
use App\Http\Requests\StoreBlogRequest;
use App\Http\Requests\UpdateBlogRequest;

class PostsController
{
    public function store(StoreBlogRequest $request) { /*...*/ }
    public function update(UpdateBlogRequest $request) { /*...*/ }
    public function delete(DeleteBlogRequest $request) { /*...*/ }
}
问题

3 个接口分别对应 StoreBlogRequestUpdateBlogRequestDeleteBlogRequest ,30个接口得对应30个 XxxRequest 文件太多。

封装想法是一个 Controller 对应一个 RequestRequest 识别需要的返回的 Rules

达到的效果

class BlogController extends Controller
{
    // BlogRequest 自动验证 store 规则
    public function store(BlogRequest $request) { /*...*/ }
    // BlogRequest 自动验证 update 规则
    public function update(BlogRequest $request) { /*...*/ }
    // BlogRequest 自动验证 delete 规则
    public function delete(BlogRequest $request) { /*...*/ }
}

实现方法

namespace App\Http\Requests;

class BlogRequest extends FormRequest
{
    public function rules() {
        // 获取请求对应的ActionMethod(); e.g. store/update/delete
        $actionMethod = $this->route()->getActionMethod();

        if (!method_exists($this, $actionMethod)) {
            return [];
        }

        // e.g. $this->>store();
        return $this->$actionMethod();
    }

    public function store() {
        return [
            'title'   => 'required|max:100',
            'content' => 'required|max:1000'
        ];
    }

    public function delete() {
        return [
            'id' => 'required|integer',
        ];
    }
}

这样就可以通过定义一个Request 规则对应一个 Controller

但问题紧接着也来了,如果要定义自定义 messageauthorize 怎么实现呢?

根据上面的实现方式,可以抽象出一个 BaseRequest 去继承 FormRequest 重写对应的方法,然后自定义的 Request 再继承 BaseRequest 专注定义验证规则即可

BaseRequest

class BaseRequest extends FormRequest 
{
    public function authorize(): bool {
        $actionMethod = $this->route()->getActionMethod() . 'Authorize';

        if (!method_exists($this, $actionMethod)) {
            return true;
        }

        return $this->$actionMethod();
    }

    public function rules(): array {
        $actionMethod = $this->route()->getActionMethod() . 'Rules';

        if (!method_exists($this, $actionMethod)) {
            return [];
        }

        return $this->$actionMethod();
    }

    public function messages(): array {
        $actionMethod = $this->route()->getActionMethod() . 'Messages';

        if (!method_exists($this, $actionMethod)) {
            return [];
        }

        return $this->$actionMethod();
    }
}

可以看到,在 BaseRequest 中,方法以 ActionMethod + 规则 实现

BlogRequest

class BlogRequest extends BaseRequest {
    public function storeRules() {
        return ['title' => 'required|max:100', 'content' => 'required|max:1000'];
    }

    public function storeMessages() {
        return ['title.required' => '标题不能为空', 'content.required' => '内容不能为空'];
    }

    public function updateRules() {
        return ['id' => 'required|integer', 'title' => 'max:100', 'content' => 'max:1000'];
    }

    public function deleteRules() {
        return ['id' => 'required|integer',];
    }

    public function deleteAuthorize() {
        return false;
    }
}

BolgController

class BlogController extends Controller
{
    // BlogRequest 自动验证 store 规则
    public function store(BlogRequest $request) { /*...*/ }
    
    // BlogRequest 自动验证 update 规则
    public function update(BlogRequest $request) {
        return response()->json([
            'status'  => 200,
            'message' => 'success',
        ],200, [], JSON_UNESCAPED_UNICODE);
    }
    
    // BlogRequest 自动验证 delete 规则
    public function delete(BlogRequest $request) { /*...*/ }
}

启动服务验证: php artisan serve

$ curl -s -d 'title=test' -X POST '127.0.0.1:8000/api/blog/after/store' | jq .
{
  "status": 400,
  "message": "内容不能为空"
}

$ curl -s -d 'id=1' -X POST '127.0.0.1:8000/api/blog/after/update' | jq .
{
  "status": 200,
  "message": "success"
}

$ curl -s -d 'id=1' -X POST '127.0.0.1:8000/api/blog/after/delete' | jq .
{
  "status": 403,
  "message": "您没有权限访问" // 为什么信息是这个,下面会说到。
}

当然,你还可以在 BaseReqeust 中定义错误返回格式等

/** 参数验证失败返回处理 */
protected function failedValidation(Validator $validator): HttpResponseException
{
    $actionMethod = $this->route()->getActionMethod() . 'FailedValidation';

    // 使用自定义错误格式,但通常不会在具体规则类里面重写,因为错误格式应该要保持一致
    // 或许需要与外部系统交互之类特殊情况就就可以重写此方法
    if (method_exists($this, $actionMethod)) {
        $this->$actionMethod();
    }

    // 默认错误格式
    $err = $validator->errors()->first();

    throw new HttpResponseException(response()->json([
        'status'  => 400,
        'message' => $err,
    ], 400, [], JSON_UNESCAPED_UNICODE));
}

请求授权验证未通过时

/** 请求授权验证未通过时(authorize方法 return false; 未通过时) */
protected function failedAuthorization()
{
    $actionMethod = $this->route()->getActionMethod() . 'FailedAuthorization';

    if (method_exists($this, $actionMethod)) {
        return $this->$actionMethod();
    }

    throw new HttpResponseException(response()->json([
        'status'  => 403,
        'message' => '您没有权限访问',
    ], 403, [], JSON_UNESCAPED_UNICODE));
}
总结这样就可以实现一个 Controller 对应一个 Request 了,不过有利有弊,减少了文件数量的同时带来的就是修改对应规则的时候需要找到对应的规则。failedValidationfailedAuthorization 统一返回错误格式也可以通过判断他们 Exception 来实现,因为它们分别抛出的异常是 ValidationExceptionAuthorizationException 。文档只能看简单的使用方法,遇到问题得多去上层看看源码,找到些另辟蹊径的处理方法。

Code

./app/Http/Controllers
├── Controllers
│   ├── AfterBlogController.php
│   ├── BeforeBlogController.php
│   ├── ...

./app/Http/Requests
├── Requests
│   ├── BaseRequest.php
│   ├── BlogRequest.php
│   ├── DeleteBlogRequest.php
│   ├── StoreBlogRequest.php
│   └── UpdateBlogRequest.php

Route

$ php artisan route:list | grep "blog"
POST | api/blog/after/delete 
POST | api/blog/after/store  
POST | api/blog/after/update 

POST | api/blog/before/delete
POST | api/blog/before/store 
POST | api/blog/before/update

github.com

https://github.com/zxr615/rewrite-pay-module/tree/main/app/Http/Controllers

特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

laravel介绍

Laravel是一套简洁、优雅的PHP Web开发框架(PHP Web Framework)。它可以让你从面条一样杂乱的代码中解脱出来;它可以帮你构建一个完美的网络APP,而且每行代码都可以简洁、富于表达力。在Laravel中已经具有了一套高级的PHP ActiveRecord实现 -- Eloquent ORM。它能方便的将“约束(constraints)”应用到关系的双方,这样你就具有了对数

Tags 标签

phplaravel

扩展阅读

加个好友,技术交流

1628738909466805.jpg