Laravel 验证类 实现 路由场景验证 和 控制器场景验证

基于不破坏框架原本用法、方便、简单的原则,实现路由场景验证和控制器场景验证。

第一步

创建 app\Http\Requests\SceneValidator.php trait

<?php

namespace App\Http\Requests;

trait SceneValidator
{
    //场景
    protected $scene = null;

    //是否自动验证
    protected $autoValidate = true;

    protected $onlyRule=[];

    /**
     *  覆盖 ValidatesWhenResolvedTrait 下 validateResolved 自动验证
     */
    public function validateResolved()
    {
        if(method_exists($this,'autoValidate')){
            $this->autoValidate = $this->container->call([$this, 'autoValidate']);
        }
        if ($this->autoValidate) {
            $this->handleValidate();
        }
    }

    /**
     * 复制 ValidatesWhenResolvedTrait -> validateResolved 自动验证
     */
    protected function handleValidate()
    {
        $this->prepareForValidation();

        if (! $this->passesAuthorization()) {
            $this->failedAuthorization();
        }

        $instance = $this->getValidatorInstance();

        if ($instance->fails()) {
            $this->failedValidation($instance);
        }
    }


    /**
     * 定义 getValidatorInstance 下 validator 验证器
     * @param $factory
     * @return mixed
     */
    public function validator($factory)
    {
        return $factory->make($this->validationData(), $this->getRules(), $this->messages(), $this->attributes());
    }


    /**
     * 验证方法(关闭自动验证时控制器调用)
     * @param string $scene  场景名称 或 验证规则
     */
    public function validate($scene='')
    {
        if(!$this->autoValidate){
            if(is_array($scene)){
                $this->onlyRule = $scene;
            }else{
                $this->scene = $scene;
            }
            $this->handleValidate();
        }
    }

    /**
     * 获取 rules
     * @return array
     */
    protected function getRules()
    {
        return $this->handleScene($this->container->call([$this, 'rules']));
    }

    /**
     * 场景验证
     * @param array $rule
     * @return array
     */
    protected function handleScene(array $rule)
    {
        if($this->onlyRule){
            return $this->handleRule($this->onlyRule,$rule);
        }
        $sceneName = $this->getSceneName();
        if($sceneName && method_exists($this,'scene')){
            $scene = $this->container->call([$this, 'scene']);
            if(array_key_exists($sceneName,$scene)) {
                return $this->handleRule($scene[$sceneName],$rule);
            }
        }
        return  $rule;
    }

    /**
     * 处理Rule
     * @param $sceneRule
     * @param $rule
     * @return array
     */
    private function handleRule(array $sceneRule,array $rule)
    {
        $rules = [];
        foreach ($sceneRule as $key => $value) {
            if (is_numeric($key) && array_key_exists($value,$rule)) {
                $rules[$value] = $rule[$value];
            } else {
                $rules[$key] = $value;
            }
        }
        return $rules;
    }

    /**
     * 获取场景名称
     *
     * @return string
     */
    protected function getSceneName()
    {
        return is_null($this->scene) ? $this->route()->getAction('_scene') : $this->scene;
    }
}

第二步

app\Providers\AppServiceProvider.php boot 方法里面添加

use Illuminate\Routing\Route;

//Route 路由自定义scene(场景方法)
Route::macro('scene',function ($scene=null){
    $action = Route::getAction();
    $action['_scene'] = $scene;
    Route::setAction($action);
});

该自定义方法用于路由场景验证,在 Route->action 增加一个 _scene 属性;用法跟 路由别名 name 一样:

Route::post('add','UserController@add')->scene('add');

Tip:如果不需要路由场景验证,则不用添加。

用法

在验证类内里 use SceneValidator

例如下面 UserRequest 验证类

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRequest extends FormRequest
{
    use SceneValidator;

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|string|unique:users',
            'email' => 'required|email|unique:users',
        ];
    }

    /**
     * 场景规则
     * @return array
     */
    public function scene()
    {
        return [
            //add 场景
            'add' => [
                'name' ,       //复用 rules() 下 name 规则
                'email' => 'email|unique:users'  //重置规则
            ],
            //edit场景
            'edit' => ['name'],
        ];
    }
}

场景规则

在验证类里面添加scene方法

    /**
     * 场景规则
     * @return array
     */
    public function scene()
    {
        // 格式  ['场景名' => [规则]]
        return [
            //add 场景
            'add' => [
                'name',        //复用 rules() 下 name 规则
                'email' => 'email|unique:users'  //重置规则
            ],
            //edit场景
            'edit' => ['name'],
        ];
    }

自动验证控制

在验证类里面添加 autoValidate 方法来控制自动验证; true:开启 | false:关闭 默认开启。

 /**
     * 设置是否自动验证
     * @return bool
     */
    public function autoValidate(){
        return false;  //关闭
    }

可以根据不同需求开启和关闭灵活控制(此处要配合路由场景验证有效;控制器场景验证则必须关闭自动验证);比如某些场景下关闭

  public function autoValidate(){
        $sceneName = $this->getSceneName();
        if(in_array($sceneName,['add'])){  //add 场景下关闭自动验证
            return  false;
        }
    }

路由场景验证

Route::post('create','UserController@create');     
Route::post('add','TestController@add')->scene('add');

控制器场景验证

Tip:控制器场景验证需要 关闭自动验证 ,通过 validate('场景名') 来调起验证;

优先级: 控制器场景验证 > 路由场景验证

public function add(UserRequest $request){
    $request->validate('add'); 
}

支持全部验证 、独立验证规则

    //全部验证 rules()下规则
    $request->validate(); 

    // 独立传验证规则
    $request->validate( [
        'name',        //复用 rules() 下 name 规则
        'email' => 'email|unique:users'  //重置规则
    ]); 

一直没有找到自己理想的场景验证,于是整理了这套场景验证,供参考;

如有更好的场景验证,求分享学习下;

由于第一次写有不足的地方,请各位谅解!

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 2年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 34

之前我看过竟然没有留言,不科学啊 还好又找到了,感谢楼主大大的分享

2年前 评论

@echoki CeRequest 里面加上

 public function autoValidate(){
        return false;
    }
3年前 评论
echoki 3年前

重写验证完美解决了 同一字段,不同场景对应不同验证规则 的问题

3年前 评论

这个在验证失败的时候是跳转页面,如何不跳转而返回错误消息 以便api使用呢

8个月前 评论
sirping (楼主) 8个月前

看评论有好多说没有走验证流程的,在控制器的方法中注入对应的Request即可

1年前 评论

好,谢谢大佬。抱歉这么晚回复你,因为没有关注。关于源码方向,您有什么指教的吗?

2年前 评论

@NoTurningBack 多写代码,不会就查文档,平时多看看框架的底层代码,学习别人思路,一步一步来

2年前 评论

还有想向大佬请教下,我该怎么学习这个框架,我自己摸索的2个月,感觉毫无头绪

2年前 评论

大佬,我想知道这个验证的原理(或者让我知道该看哪些文档),这个困扰我很久了。我是会用,但是那样不妥。

2年前 评论

@黎明 看了5.5 ValidatesWhenResolvedTrait 函数名变了 并且与 SceneValidator 函数有冲突 解决办法

  1. SceneValidator 下的 validateResolved() 函数名改为 validate file
  2. SceneValidator 下的 validate() 改成其他名字 file
3年前 评论
黎明 3年前

请问 laravel 5.5 可以用吗?我走了一遍控制器场景验证流程,没有生效

3年前 评论

我是通过路由验证的,一直不生效,laravel8.5

路由配置

file

控制器

file

验证场景

file

配置AppServiceProvider

file

测试效果,没有验证生效

file

3年前 评论
sirping (楼主) 3年前
gboy229 (作者) 3年前
Pno1 1年前

可以根据不同需求开启和关闭灵活控制;比如某些场景下关闭

  public function autoValidate(){
        $sceneName = $this->getSceneName();
        if(in_array($sceneName,['add'])){  //add 场景下关闭自动验证
            return  false;
        }
    }

您上面的场景不生效,我是通过控制器来验证

file
autoValidate()获取不到场景名称

file
不知道路由是否生效,没有测试

3年前 评论
sirping (楼主) 3年前

嗯嗯 解决了,还是大佬细心 文档写的很好 是我自己不够细心 再次感谢教导 感谢

3年前 评论

@echoki

file 这个是应该是true

关闭自动验证是 autoValidate() 方法 不是 authorize()

  public function autoValidate(){
        return false;
    }
3年前 评论

file

我用的是CeRequest 没有用UserRequest 大佬

3年前 评论

我按照上面的一比一搞 为啥就是不走场景呢?

3年前 评论
sirping (楼主) 3年前
echoki (作者) 3年前
sirping (楼主) 3年前

请问laravel7 能用吗? 我试了没有走验证

3年前 评论
sirping (楼主) 3年前

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