Laravel 驗證類 實現 路由場景驗證 和 控制器場景驗證

sirping發表於2019-12-28

基於不破壞框架原本用法、方便、簡單的原則,實現路由場景驗證和控制器場景驗證。

建立 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'  //重置規則
    ]); 

一直沒有找到自己理想的場景驗證,於是整理了這套場景驗證,供參考;

如有更好的場景驗證,求分享學習下;

由於第一次寫有不足的地方,請各位諒解!

相關文章