偶然的一次,我发现了在工作项目中出现的一个bug:针对手机号做校验,在处理区号格式的时候,我期望直接判断区号是数字就通过,于是我用了is_numeric,结果发现跟我预期的不一样,因为实际拿到的数据,有可能是“+86”或者是“86”,而我仅仅是期望“86”才能通过验证。
在这个时候我想到了,字符串的“+”被识别为正负号了,所以“+86”或者“-86”都会被通过校验。
于是我又去检索了一下PHP的is_numeric,发现网上有挺多这类博客:关于is_numeric的漏洞,我好奇点开了一下,里面提到了这种说法:is_numeric对16进制的数据同样也会被识别为通过,例如字符串“0x4B0”,会识别为数字。我还是头一次知道原来这函数针对这种类型字符串也会被识别为true,于是我尝试了一下:
明明是false,哪有什么被识别为true。于是我又找了其他几篇博客,发现同样有人说这个漏洞。心里就纳闷了,我看到那些博客时间都是14年的,于是想到会不会是PHP版本的问题,于是我切换了一下PHP的版本,再次尝试了一下:
果然,我切到5.6之后,返回了true。紧接着我在官方文档找到了关于该函数的changelog:Strings in hexadecimal (e.g. 0xf4c3b00c
) notation are no longer regarded as numeric strings, i.e. is_numeric() returns FALSE
now,附上地址。所以看到这里,那些文章提到的其实是没有问题的,只是版本不一致所以才有了以上疑惑。
回到正题,那怎么验证我的期望数据?答案是利用函数:ctype_digit(string $text),如果 text
字符串是一个十进制数字,就返回 TRUE
;反之就返回 FALSE
于是,我使用了各种类型数字字符串如“1234567898784120”、“0x4be”、“1e3”、“+86”、“-86”等做校验,除了第一个是纯数字外,其他的都返回false,看来满足要求。
这里重点提一下接受的参数是字符串,如果传递的是整数,它会做转换,将得到异常的情况,具体可看注释。
此外,在官方介绍Ctype时,里面说到了:如果在满足要求的情况下,优先使用Ctype函数,而不是正则表达式或者对应的 "str_*"
和 "is_*"
函数,因为ctype 使用的是原生 C 库,所以会有明显的性能优势。同时,Ctype还有判断字母,数字等函数。
在了解到以上差异之后,看来日常使用过程中,判断字符串真正的是纯数字,更多满足要求的判断应该是ctype_digit,而不是is_numeric。