来!实现一个表单场景验证

有时候觉得每个验证都要写个文件,有时候会有大量的重复字段,总想偷个懒,感觉tp的场景验证还是可以借鉴的。

1、先让所有的验证文件都继承 BaseRequest;BaseRequest再继承FormRequest;
2、然后在 BaseRequest 内改写 createDefaultValidator 方法;

    protected function createDefaultValidator(ValidationFactory $factory)
    {
        $rules = method_exists($this, 'rules')
            ?
            (method_exists($this, 'scene') ? $this->container->call([
                $this, 'getRules',
            ]) : $this->container->call([$this, 'rules']))
            :
            [];
        $validator = $factory->make(
            $this->validationData(), $rules,
            $this->messages(), $this->attributes(),
        )->stopOnFirstFailure($this->stopOnFirstFailure);

        if ($this->isPrecognitive()) {
            $validator->setRules(
                $this->filterPrecognitiveRules($validator->getRulesWithoutPlaceholders()),
            );
        }

        return $validator;
    }

这里有验证规则获取的是 getRules() 方法;

3、添加 getRules() 方法;

public function getRules()
    {
        $rules  = $this->container->call([$this, 'rules']);
        $scene  = $this->container->call([$this, 'scene']);
        $action = request()->route()->getActionName();
        [$class, $methodName] = explode('@', $action);
        $rulesArr = [];
        if (!isset($scene[$methodName])) {
            return $rules;
        }
        foreach ($scene[$methodName] as $sceneKey => $sceneItem) {
            if (is_int($sceneKey) && isset($rules[$sceneItem])) {
                $rulesArr[$sceneItem] = $rules[$sceneItem];
            }
            if (is_string($sceneKey) && isset($rules[$sceneKey])) {
                $rulesArr[$sceneKey] = $sceneItem;
            }
        }
        return $rulesArr;
    }

这里是获取路由访问的方法名,根据方法名来获取场景对应的验证数组;

4、是具体的验证文件里添加以方法名为场景的方法 scene();

public function scene(): array
    {
        return [
            'login'         => ['email', 'password'],
            'register'      => ['email', 'password', 'name'],
            'miniapp_login' => ['code'],
        ];
    }

5、最后上完整代码;
UserRegisterRequest.php;重新failedValidation()方法,只是为了接口返回一条错误信息就可以了,和本次要说的场景验证关系不大;

<?php

namespace App\Api\Requests;

use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;

class BaseRequest extends FormRequest
{
    protected function failedValidation($validator)
    {
        $error = $validator->errors()->all();
        throw new HttpResponseException(fail([600000, $error['0']]));
    }

    protected function createDefaultValidator(ValidationFactory $factory)
    {
        $rules = method_exists($this, 'rules')
            ?
            (method_exists($this, 'scene') ? $this->container->call([
                $this, 'getRules',
            ]) : $this->container->call([$this, 'rules']))
            :
            [];
        $validator = $factory->make(
            $this->validationData(), $rules,
            $this->messages(), $this->attributes(),
        )->stopOnFirstFailure($this->stopOnFirstFailure);

        if ($this->isPrecognitive()) {
            $validator->setRules(
                $this->filterPrecognitiveRules($validator->getRulesWithoutPlaceholders()),
            );
        }

        return $validator;
    }

    public function getRules()
    {
        $rules  = $this->container->call([$this, 'rules']);
        $scene  = $this->container->call([$this, 'scene']);
        $action = request()->route()->getActionName();
        [$class, $methodName] = explode('@', $action);
        $rulesArr = [];
        if (!isset($scene[$methodName])) {
            return $rules;
        }
        foreach ($scene[$methodName] as $sceneKey => $sceneItem) {
            if (is_int($sceneKey) && isset($rules[$sceneItem])) {
                $rulesArr[$sceneItem] = $rules[$sceneItem];
            }
            if (is_string($sceneKey) && isset($rules[$sceneKey])) {
                $rulesArr[$sceneKey] = $sceneItem;
            }
        }
        return $rulesArr;
    }

    public function messages(): array
    {
        return [
            'required'       => '缺少参数::attribute',
            'numeric'        => ':attribute 必须为数字',
            'email'          => '邮箱格式不正确',
            'integer'        => ':attribute 必须为整数',
            'gt'             => ':attribute 取值范围错误',
            'gte'            => ':attribute 必须大于等于:value',
            'max'            => ':attribute 最多:max个字符',
            'min'            => ':attribute 最少:min个字符',
            'alpha_dash'     => ':attribute 只能为字母、数字、短破折号、下划线',
            'in'             => ':attribute 取值范围只能 :values',
            'unique'         => ':attribute 已存在',
            'string'         => ':attribute 格式错误',
            'regex'          => ':attribute 格式错误',
            'exists'         => ':attribute 信息不存在',
            'date_format'    => ':attribute 格式为不正确',
            'after_or_equal' => ':attribute 必须大于等于 :date',
        ];
    }
}

PostRequest.php

<?php

namespace App\Api\Requests;

class PostRequest extends BaseRequest
{
    // 验证规则
    public function rules(): array
    {
        return [
            'email'    => 'required|email',
            'password' => 'required|min:6|max:16',
            'code'     => 'required',
            'name'     => 'required|string|min:2|max:10',
        ];
    }

    public function attributes(): array
    {
        return [
            'name'     => '姓名',
            'email'    => '邮箱',
            'password' => '密码',
            'code'     => 'code',
        ];
    }

    // 实现场景验证,key对应控制器里的方法名;
    public function scene(): array
    {
        return [
            'login'         => ['email', 'password'],
            'store'         => ['email', 'password', 'name'],
            'miniapp_login' => ['code'],
        ];
    }

// 还可以重新定义规则
//    public function scene(): array
//    {
//        return [
//            'login'         => [
//                'email' => 'required|unique|email',
//                'password' => 'required|min:6|max:16',
//            ],
//            'register'      => ['email', 'password', 'name'],
//            'miniapp_login' => ['code'],
//        ];
//    }

}

6、最后在控制器内使用;

    /**
     * 新增
     * @param  ArticleRequest  $request
     * @return JsonResponse
     * @throws BusinessException
     */
    public function store(PostRequest $request): JsonResponse
    {

    }

到此 完成了我想要的目的。
各位大佬,有更好的想法还请不要吝啬交流。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 6

:joy: 我还是始终觉得明确单一职责是没啥问题的。你这个简单的还好,如果遇到复杂的场景,这个应付不过来。

2个月前 评论
洪利 (楼主) 2个月前

心智负担太高了,如果真的有重复的情况,可以通过 extends 或者 trait 来解决,我觉得这样解决不是一个优雅的方式。

叠甲: 无恶意,一个东西有多种实现方式,没有绝对的对与错

2个月前 评论

如果直接继承之前相同表单的验证器,然后重写其中的方法会不会更优雅一点

class PutRequest extends PostRequest
{
    public function rules()
    {
        return [
            ...parent::rules(),
            'name' => 'required',
            // ...
        ];
    }
}
2个月前 评论
sunny123456 2个月前
唐章明

1、场景使用复杂、阅读不方便、浪费心智,不如分开写多个验证类简单。

2、压根不实用,比如创建数据的时候要验证title唯一,修改的时候title唯一并且排除当前修改行数据,你的场景压根无法解决这种问题

1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!