Yii2 的输入验证是在 Model(ActiveRecord)里做的,对于现在前后端分离的接口开发来说,显得有点不是很顺手。现在的 PHP 框架,比如 Laravel、ThinkPHP 通常的做法都是采用的全局验证器,在到 Controller 的 Action 前就已经验证好了。下面我们就来改造一下 Yii2,让其拥有全局验证器。

首先建立一个虚拟的 Model,命名为 ParamsValidate 继承自 ActiveRecord 代码如下:

<?php
/**
 * ParamsValidate.php
 *
 * Created by Samdlcong.
 */

namespace common\models;

use yii\db\ActiveRecord;

class ParamsValidate extends ActiveRecord
{
    /**
     * @var array 验证规则
     */
    private $_rules = [];
    private $_attributes = [];

    // 设置验证规则
    public function setRules($rules)
    {
        $this->_rules = $rules;
        foreach ($rules as $item) {
            $this->_attributes = array_unique(array_merge($this->_attributes, (array)$item[0]));
        }
    }

    // 重写获取验证规则
    public function rules()
    {
        return $this->_rules;
    }

    // 设置可用属性列表
    public function attributes()
    {
        return $this->_attributes;
    }
}

然后建立全局验证器基类 ParamsValidate 继承 Component 组件,代码如下:

<?php
/**
 * ParamsValidate.php
 *
 * Created by Samdlcong.
 */

namespace api\validates;

use api\helpers\RequestHelper;
use common\models\ParamsValidate as ParamsValidateModel;
use yii\base\Component;
use yii\web\Response;
use Yii;

class ParamsValidate extends Component
{
    /**
     * @var ParamsValidate 模型
     */
    private $model = null;

    protected $rule = [];

    public function init()
    {
        parent::init();
        $this->model = new ParamsValidateModel();
    }
    /**
     * @param array $data 数据项
     * @param array $rules 验证规则
     * @return bool
     */
    public function validate($data, $rules)
    {
        // 添加验证规则
        $this->model->setRules($rules);
        // 设置参数
        $this->model->load($data, '');
        // 进行验证
        return $this->model->validate();
    }


    public function goCheck(){
        $data = RequestHelper::params();
        // 添加验证规则
        $this->model->setRules($this->rule);
        // 设置参数
        $this->model->load($data, '');
        // 进行验证
        $this->model->validate();
        if($this->model->hasErrors()){
            $data = [
                'code' => 400,
                'msg' =>  array_column(array_values($this->model->errors),0),
                'errorCode' => 10001,
            ];
            $response = Yii::$app->response;
            $response->statusCode = 400;
            $response->format = Response::FORMAT_JSON;
            $response->data = $data;

            $response->send();
        }
        return true;
    }

    public function __call($name, $params)
    {
        if ($this->model->hasMethod($name)) {
            return call_user_func_array([$this->model, $name], $params);
        } else {
            return parent::__call($name, $params);
        }
    }
}

新建 goCheck 方法,用来验证是否符合 rule,如果不符合则返回错误信息。由于 Yii 没有提供直接获取 GET 和 POST的方法,这里做了一个 helper 来获取

<?php
/**
 * RequestHelper.php
 *
 * Created by Samdlcong.
 */

namespace api\helpers;

use Yii;
class RequestHelper
{
    public static function params(){
        return array_merge(Yii::$app->request->getQueryParams(),Yii::$app->request->getBodyParams());
    }
}

所有的验证器都继承这个基类,重写验证 rule,比如:

<?php
/**
 * PidMustBePositiveInt.phpp
 *
 * Created by Samdlcong.
 */
namespace api\validates;

class PidMustBePositiveInt extends ParamsValidate{
    protected $rule = [
        ['product_id',  'compare','compareValue' => 0, 'operator' => '>','message'=>'必须正整数']
    ];
    
}

自带的核心验证器已经满足大部分的需求,如果需要自定义则需要新建独立验证器 validator 文件继承yii\validators\Validator

在 Controller 里这样使用:

<?php
/**
 * TestController.php
 *
 * Created by Samdlcong.
 */

namespace api\controllers;

use api\validates\PidMustBePositiveInt;

class TestController extends ApiBaseController
{
    public function actionTest(){
       (new PidMustBePositiveInt())->goCheck();
        return 'ok';
    }
}

至此 Yii 的全局验证器就写好了,如果需要进一步的话可以写到注解里,然后 beforeAction 里用反射的方式来取,隐藏 (new PidMustBePositiveInt())->goCheck(); Laravel 的验证器就是这么干的,并且该组件可以独立使用,这也是另外一种选择。