米粒人生 2016-11-07 10:30:06 7809次浏览 5条评论 19 7 0

Yii2的Validator是非常好用的一类方法,它辅助Model层,完成对数据的校验。现在核心的验证器有这么几类:

[[BooleanValidator]] - 要求属性必须为Bool类型
[[CompareValidator]] - 完成两个属性的比较
[[DateValidator]] - 要求属性必须为日期类型
[[EachValidator]] - 要求数组的每个元素必须满足某个条件
[[EmailValidator]] - 要求属性必须为邮件格式
[[ExistValidator]] - 要求该属性必须存在于此模型或者别的模型个某个属性当中
[[StringValidator]] - 要求该属性必须为字符串
[[RangeValidator]] - 要求属性必须在某个范围之内取值
.....

使用起来,它们都有一样的面孔,那就是,在复写的model::rules方法里面增加一条规则:

['country', 'in', 'range' => ['china', 'usa'], 'message' => 'the country is wrong']

这条规则就可以保证country这个属性必须在china和usa之间二选一,否则就会报'the country is wrong'错误。是不是非常的简单?
这个家族的验证器在活动记录ActiveRecord的使用时非常有用,能保证你插入数据库的数据是正确无误的。
已有的校验类虽然很丰富,但是毕竟不能满足我们全部的对数据校验需求,我们希望能扩展已有的验证器。有没有这样一个方法,既能能以这样简单的方式使用,又能方便我们自己定义校验规则?这就是今天要跟大家分享的内容。
直接上代码:

class RegexValidator extends Validator
{
    /**
     * @var string|array 所要采用的验证方法,可以为string,也可以为如果个方法组成的array
     * 所有的方法必须属于RegexValidator
     */
    public $method = null;
    /**
     * @var array 验证的方法列表
     * 方法必须属于RegexValidator
     */
    private $_methodArray = [];

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        $this->_methodArray = (array)$this->method;
        if(empty($this->_methodArray)){
            throw new InvalidConfigException("Configuration error:no validating method are found!");
        }
        foreach($this->_methodArray  as $method){
            if(!$this->hasMethod($method)){
                throw new InvalidConfigException("Validating method:\"{$method}\" does not exits!");
            }
        }
    }

    /**
     * @inheritdoc
     */
    public function validateAttribute($model, $attribute)
    {
        $value = $model->$attribute;
        //将错误信息转化为数组,数组元素对应[_methodArray]的验证方法
        $this->message = (array)$this->message;
        foreach($this->_methodArray  as $k => $method){
            $ret = call_user_func([$this, $method], $value);
            if($ret === false){
                $error = isset($this->message[$k]) ? $this->message[$k] : Yii::t('yii', '{attribute} is invalid.');
                $this->addError($model, $attribute, $error);
            }
        }
    }

    /**
     * @inheritdoc
     */
    protected function validateValue($value)
    {
        $this->message = (array)$this->message;
        foreach($this->_methodArray as $k => $method){
            $ret = call_user_func([$this, $method], $value);
            if($ret === false){
                $error = isset($this->message[$k]) ? $this->message[$k] : Yii::t('yii', "\"{$value}\" is invalid specified by the validator:". static::className() ."::$method");
                return [$error, []];
            }
        }
        return null;
    }

    /**
     * @inheritdoc
     */
    public function clientValidateAttribute($model, $attribute, $view)
    {

    }   

   //...这里是你的逻辑…… 
   
    /**
     * 由26个大写英文字母组成的字符串
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function uperchars($data = null)
    {
        $_pattern = "/^[A-Z]+$/";
        return self::_regex($_pattern, $data);
    }

    /**
     * 由26个小写写英文字母组成的字符串
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function lowerchars($data = null)
    {
        $_pattern = "/^[a-z]+$/";
        return self::_regex($_pattern, $data);
    }

    /**
     * 由数字和26个英文字母组成的字符串
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function numschars($data = null)
    {
        $_pattern = "/^[A-Za-z0-9]+$/";
        return self::_regex($_pattern, $data);
    }

    /**
     * 手机号码
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function mobile($data = null)
    {
        $_pattern = "/^(0|86|17951)?(13[0-9]|15[012356789]|1[78][0-9]|14[57])[0-9]{8}$/";
        return self::_regex($_pattern, $data);
    }

    /**
     * Email
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function email($data = null)
    {
        $_res = filter_var($data, FILTER_VALIDATE_EMAIL);
        return empty($_res) ? false : true;
    }

    /**
     * 邮编
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function postcode($data = null)
    {
        $_pattern = "/^[1-9]\d{5}(?!\d)$/";
        return self::_regex($_pattern, $data);
    }

    /**
     * 中文
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function zh($data = null)
    {
        $_pattern = "/^[\x{4e00}-\x{9fa5}]+$/u";
        return self::_regex($_pattern, $data);
    }

    /**
     * URL地址
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function url($data = null)
    {
        $_res = filter_var($data, FILTER_VALIDATE_URL);
        return empty($_res) ? false : true;
    }

    /**
     * 身份证
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function identity($data = null)
    {
        $_pattern = "/^(^\d{15}$)|(^\d{17}([0-9]|X)$)$/";
        return self::_regex($_pattern, $data);
    }

    /**
     * IPv4
     * @param $data mixed 数字或者字符串
     * @return bool
     **/
    public static function ip($data = null)
    {
        $_res = filter_var($data, FILTER_VALIDATE_IP);
        return empty($_res) ? false : true;
    }

    /**
     * 匹配正则公共方法
     * @param $pattern string 匹配模式
     * @param $subject string 对象
     * @return bool
     */
    private static function _regex($pattern, $subject = null)
    {
        if ($subject === null)
        {
            return false;
        }
        if (preg_match($pattern, $subject))
        {
            return true;
        }
        return false;
    }

}

首先,必须继承yii\validators\Validator,并且复写validateAttributevalidateValue方法。validateAttribute是验证属性用的,调用$module->validate()时会对其隐式的调用;validateValue则可以不依赖Model独立使用。clientValidateAttribute则是在客户端实现数据校验的部分(这部分等待聪明的你去DIY)。init实现初始化的功能。
复写了基础的几个Validator方法,然后就是我们自己的校验数据的逻辑:

[[zh]] - 校验数据是否为中文
[[postcode]] - 校验数据是否为邮编
[[mobile]] - 校验数据是否为手机号码
……

接下来,当然是最精彩的部分——如何使用?有三种方式可以方便您调用:

1.用在数据模型model的rules方法里面:

['name', RegexValidator::className(), 'method' => 'lowerchars', 'message' => '名字必须全为小写字母']
[
    ['mobile', 'status'],
    RegexValidator::className(),
    'method' => ['mobile', 'zh'],
    'message' => ['手机格式不正确''必须为中文']
]

这里需要在Model里引入RegexValidator类,并用RegexValidator::className()代替核心验证器'in','string','exsit'等;
规则里的'method'是你自己定义的(静态)方法,你的校验逻辑之所在。可以单个引用,也可以为数组,当为数组时对应的错误信息'message'也得为数组,而且错误信息与之对应。
当调用$model->validate(),如果不满足以上的条件这个方法就会返回false,而且在$model->getErrors()里面会返回具体的错误信息:

[
    'name' => [
        '名字必须全为小写字母',
    ],
    'mobile' => [
        '手机格式不正确',
    ],
    'zh' => [
        '必须为中文',
    ],  
]

返回的错误信息和核心验证器格式是完全一样的。

2.脱离model独立使用,必须要配置[method]参数:

$valid = new RegexValidator([
    'method' => ['zh', 'negative'],
    'message' => ['必须为中文', '必须为负数'],
)];
$valid->validate($value, $error);
if($error){
    echo $error;
}

同样的和核心验证器的使用方法相同;

3.直接调用RegexValidator里的各个静态方法进行验证

$value = 'Abc';
$ret = RegexValidator::mobile($value);
if(!$ret){
	echo ...;
}

这是最简单调用方法,此时无法使用错误提示。
这个类是可以扩展的,您可以将自己的逻辑在[[number]]以降继续添加。

此文为抛砖引入之作,欢迎大家继续在拙作上继续添砖加瓦,使得Yii2数组验证器的功能更加强大!

觉得很赞
  • 评论于 2016-11-07 11:57 举报

    前排 分享是大家共同进步的阶梯 献上我的敬意

    1 条回复
    评论于 2016-11-07 18:46 回复

    好的,谢谢!最近在解剖Yii2源代码,以后会分享更多东西!

  • 评论于 2016-11-07 12:00 举报

    表示感谢!

    1 条回复
    评论于 2016-11-07 18:46 回复

    一起进步!

  • 评论于 2016-11-07 12:03 举报

    谢谢分享。

    1 条回复
    评论于 2016-11-07 18:46 回复

    共同成长!

  • 评论于 2017-09-12 17:46 举报

    刚好需要,收藏了,谢谢楼主分享!

  • 评论于 2018-03-07 13:53 举报

    文章不错,针对数组的验证yii2本身就一个each而已,但是满足不了大众的需求。那么扩展自己项目的验证需求就非常有必要了。楼主开了个好头。

    1 条回复
    评论于 2018-03-07 14:12 回复

    是的,简单的扩展可以就在我这代码下面继续写;复杂的就重新写一个Validator

您需要登录后才可以评论。登录 | 立即注册