下面这段代码是Laravel自带的表单验证的语法,不太了解的可以先查看文档
'group_num' => 'min:1|max:21' // 也可以使用between替换min和max
我们期望的结果是能校验group_num
字段最小值是1,最大值是21
but !!!
...
当我单元测试的时候发现,竟然校验通过了!
// 单元测试代码
$warehouseId = 1;
$prods = [
[
'prod_id' => 1,
'group_num' => 111,
'location' => 1,
]
];
$warehouseLogic = new WarehouseLogic();
$result = $warehouseLogic->addProds($warehouseId, $prods);
$this->assertEquals(ErrSvc::ERR_OK, $result['code']);
文档分析
我们看一下文档
min:value验证中的字段必须具有最小值。字符串、数字、数组或是文件大小的计算方式都用 size 方法进行评估。
文章提到数字使用size方法进行评估,我们看一下size
方法的文档
size:value验证的字段必须具有与给定值匹配的大小。对于字符串来说,value 对应于字符数。对于数字来说,value 对应于给定的整数值。对于数组来说,
size 对应的是数组的 count 值。对文件来说,size 对应的是文件大小(单位 kb )。
代码分析
文档一切正常,我们翻一翻代码试着分析下原因
我们找到验证类的文件并打开:\vendor\laravel\framework\src\Illuminate\Validation\Validator.php
大约在1180行,我们看到validateMin()
方法
validateMin()
第一行代码,是对参数个数进行验证的,可以pass掉第二行代码,调用了getSize()方法,并对getSize()返回结果直接进行大小比较,问题很有可能就出现在getSize()方法身上
我们看一下getSize()的代码
getSize()
我们可以看到if里面判断了如果值是数字类型
并且$hasNumeric
就直接返回原始值,如果返回原始值的话,validateMin()
方法应该会正确校验,所以if条件应该是不成立
的
原因很可能就在$hasNumeric这个变量上
$hasNumeric调用了$this->hasRule($attribute
,$this->numericRules
)方法,并传了两个参数过去
$attribute
:当前属性名
$this->numericRules
:['Numeric', 'Integer']
然后我们看一下$this->hasRule()
方法究竟做了什么?
hasRule方法直接调用了$this->getRule()
方法,并且将参数原封不动传递过去
我们看一下getRule()方法干了什么?
getRule()
我们已知$attribute
是当前字段名,比如文章举例用的字段group_num
$this->rules
其实就是字段+校验规则拼装的数组,格式如下:
既然第一个if语句的两个变量都知道了,我们就能判断出第一个条件是不成立的,我们继续看接下来的代码
代码是对当前需要校验字段的规则进行遍历,并且格式化
list($rule, $parameters) = $this->parseRule($rule);假设是上图中的group_num字段,他有3个校验规则,分别是:required、min、max
第一次循环
$rule就是Required,$parameters为空
第二次循环
$rule就是Min,$parameters就是[1]
我们会发现parseRule($rule)
后,会对规则进行in_array的判断,$rules
是参数(['Numeric', 'Integer'])上文有写
假设我们此时的字段是group_num:第一个规则是required,条件不成立;
第二个规则是Min,条件依然不成立;
第三个规则是Max,条件还是不成立!
getRule()返回值:条件都不成立,方法走完,没有任何返回值,返回值为null
hasRule()返回值:回到hasRule()方法,会对getRule()方法值进行is_null(),并进行逻辑非处理(!),所以返回值为false
我们接着回到getSize()
方法,此时我们就知道$hasNumeric
的值是false
所以下面的if条件都不成立
,最后Laravel使用mb_strlen()
对我们数字类型的值进行了长度计算!!!
解决问题
既然我们知道原因在于Laravel对当前字段所有规则进行了in_array($rule, ['Numeric', 'Integer'])
所以解决思路就是,如果我们要对字段进行大小进行范围校验,我们需要把规则修改成:
'group_num' => 'integer|min:1|max:21'
所以文章开头的校验,对于数值类型的字段,是错误的!
其实这不是一个BUG,单纯的是Laravel的校验机制,不过Laravel文档写的很模糊!
所以大家在开发的时候记得一定要认真测试
!
原文在自己的博客:Laravel一次单元测试发现的’BUG’,分析并解决问题 - 木鱼博客