第二层内功 - 表单模型 - 验证器

  • 作者:KK

  • 发表日期:2017.2.19


rules返回的第一个数组元素都会使用一个验证器,包括自定义验证器,几乎所有验证器的介绍都能从中文官方权威指南 - 高级专题 - 核心验证器这里看到

但中文官方文档中有部分内容是英文的翻译不完全,而且内容与英文的也没有完全对上,不够详细,想学精的同学可以看英文官方权威指南 - Specail Topics - Core Validators

我这里不会将所有验证器一个个拿出来讲解


先讲几个常见的

public function rules(){
	return [
		//账号 必须是邮箱格式
		['account', 'email'],
		
		//API地址 必须是URL格式
		['apiUrl', 'url'],
		
		//年龄 必须是整数,值的范围从18到200,就是要求成年人
		['age', 'integer', 'min' => 18, 'max' => 200],
		
		//工资 必须是数字,不管有没有小数
		['salary', 'number'],
		
		//用户名必须是字符串,长度2到10个字符
		['username', 'string', 'length' => [2, 10]],
		
		//类型 必须是91、22、13和none这些值之一
		['level', 'in', 'range' => [91, 22, 13, 'none']],
		
		//类型 必须 不是 91、22、13和none这些值之一,注意后面的not定义,其实就是not in
		['level2', 'in', 'range' => [91, 22, 13, 'none'], 'not' => true],
		
		//记住登录 必须是boolean类型,0表示否,1表示是,满足前端只要传0或1的设计
		['rememberLogin', 'boolean'],
		
		//开始时间 必须是Y-m-d日期格式
		['startTime', 'date', 'format' => 'php:Y-m-d'],
	];
}

验证器的别名与类名

规则的第2个字符就是校验的特性描述符,其实那是一个验证器别名

比如说email这个别名,指的其实是yii\validators\EmailValidator

有哪些别名的验证器可以用,都怎么使用,官方文档都有给出说明,可是怎么知道哪个别名指哪个验证器类呢?

其实也不难,命名都相近的嘛,看看yii\validators目录下有哪些类,跟别名对一下就知道了,如果实在不知道,请见yii\validators\Validator::$builtInValidators这个属性,它是内置的验证器别名与类名之间的映射定义

其中能发现date、dateTime、time三个验证器其实是同一个类,只是有不同的type默认值


验证器的属性

如果只要验证个属性类型是string,只要这样就行:

public function rules(){
	return [
		['username', 'string']
	];
}

可是空字符串也是个string呀坑爹……甚至人家有999个的超长字长呢?

所以还要说明length

public function rules(){
	return [
		['username', 'string', 'length' => [2, 10]]
	];
}

你怎么记得string还能定义length这个key?整个框架那么多验证器,再比如integer又能定义min和max,肯定记不住的,特别好久不用的时候更加记不住,都得查官方文档

可是官方文档一般只会讲平常用得到的

其实验证器可以额外附加的key就是这些验证器的属性,比如你能在yii\validators\StringValidator里找到$length这个属性

那就很明郎了!校验规则除了第1个数组元素字符是要校验的属性名,第2个是规则别名,其它key部分实际上就是对应验证器的属性名称!——Yii又在玩属性注入这个玩法套路还用在这个规则定义里

这下慢慢认识到配置一个类的威力与影响了吧,我比较欣赏这个底层设计的运用,实现并不高明,但从底层支撑各功能的使用表现效果不错!它降低了我们找文档解释的成本,有疑问直接看一个类的属性注释就知道用途,所以连配置文件都不需要开章节说明了,包括各组件什么的配置都从来没有文件,直接看类的属性


自定义验证器

除了使用那些自带的验证规则以外,如果它们不能满足你的校验需求,还可以自己定义验证器:

public function rules(){
	return [
		//验证规则是一个方法名称,这个方法指向当前模型的一个public方法
		['username', 'myMethod']
	];
}

public function myMethod(){
	if(strpos($this->username, 'fuck!') !== false){
		$errorFlag = 'username'; //随便,一般用被校验的属性名,比如 username
		$this->addError($errorFlag, '用户名称出现了敏感词汇!');
	}
}

然后validate的时候就会自动调用myMethod方法来按照你的逻辑去校验username了

当校验不成功的时候就调用addError来添加一个错误消息,这样的话外面就能通过errorsfirstErrors属性来取得错误消息了


each验证器

如果要验证的属性是一个数组,希望里面每一个元素都必须是整数的话,可以用这个验证器

public function rules(){
	return [
		['deleteIds', 'each', 'rule' => ['integer']]
	];
}

each就是要求每一个元素都符合rule的要求,而rule的书写方式又是一个数组,里面第一个元素是验证器别名,其它用key来定义这个验证器的更多内容就是了(你就理解为验证器嵌套吧),比如

public function rules(){
	return [
		['deleteIds', 'each', 'rule' => ['integer', 'min' => 1]]
	];
}

和平时使用验证规则一样是相通的,知道integer是指yii\validators\NumberValidator后,找到里面的属性$min再定义到这个rule里进行属性注入即可


exist验证器

经常用于校验一个ID属性什么的是否在数据库里存在,比如这样:

public function rules(){
	return [
		['id', 'exist', 'targetClass' => 'app\models\User']
	];
}

targetClass指定了目标类,目标类必须是一个AR模型,然后exist验证器就会去查一下AR模型对应的表中有没有WHERE id = $表单->id的记录,有的话就认为校验通过


当表单模型属性与AR模型表字段属性命名不一致时的存在性校验

假设有一个收发消息的功能,消息记录表有个sender_id(发送者)和receiver_id(接收者),表单模型要往user表中校验id字段,但表单模型是这样定义属性的:

public $senderId = 0;

public $receiverId = 0;

如果这样定义规则:

public function rules(){
	return [
		['senderId', 'exist', 'targetClass' => 'app\models\User'],
		['receiverId', 'exist', 'targetClass' => 'app\models\User'],
	];
}

就会造成验证器去找user表的senderId字段和receiverId字段,但并没有这两个字段啊,查询就会出错

实际上我们希望找id字段来确定用户是否存在,可以通过targetAttribute来指定模型的哪个属性(字段)

public function rules(){
	return [
		['senderId', 'exist', 'targetClass' => 'app\models\User', 'targetAttribute' => 'id'],
		['receiverId', 'exist', 'targetClass' => 'app\models\User', 'targetAttribute' => 'id'],
	];
}

如果有更多需求,详细用法请见官方文档


unique 唯一验证器

这个通常用来校验注册邮箱或手机号什么的

public function rules(){
	return [
		['email', 'unique', 'targetClass' => 'app\models\User'],
	];
}

就是校验user表的email字段是否有相同的值,有就不通过,没有就可以;也能像exist验证器那样通过targetAttribute来指定要校验的字段


预处理

一些string类型的输入参数我们通常都希望提前trim一下空格,不然也会成功通过string的length校验(不会自动trim)

那难道先在控制器里取值,trim掉再传给表单模型吗?——不用,直接用trim过滤器就行

public function rules(){
	return [
		[['username', 'email'], 'trim'],
		['username', 'string', 'length' => [2, 10]],
		['email', 'email'],
	];
}

默认值

有些字段不是required的,或者有默认值的,方法1是在类里面声明的时候就定义默认值:public $isDelete = 1

要不就在规则里定义默认值:

public function rules(){
	return [
		//如果这样我宁愿默认在属性定义里,做人不能太死脑筋吧
		['isDelete', 'default', 'value' => 1],
		
		//支持动态计算默认值
		[['from', 'to'], 'default', 'value' => function ($model, $attribute) {
			return date('Y-m-d', strtotime($attribute === 'to' ? '+3 days' : '+6 days'));
		}],
	];
}