第二层内功 - 表单模型 - 验证器 ¶
作者: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来添加一个错误消息,这样的话外面就能通过errors
或firstErrors
属性来取得错误消息了
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'));
}],
];
}