1. 起因
老项目里一段权限校验逻辑用 == 比较用户角色 id,结果测试同学传了个奇怪的字符串进去,校验直接被绕过了。查出来是 PHP 的弱类型比较在作怪,正好把这些年踩过的几个 == 相关的坑整理一下。
2. 字符串和数字比较,字符串会被隐式转换成数字
1 | var_dump(0 == "abc"); // PHP 8 之前: true,PHP 8 之后: false |
PHP 8 之前,数字和非数字字符串比较时,会把字符串转成 0 再比较,所以 0 == "abc" 在 PHP 7 里是 true,这也是当年著名的”PHP 类型比较魔术”系列坑的源头之一——如果一个字段本来存的是哈希字符串,拿去跟 0 或者 false 做 == 比较,很容易被意外命中。PHP 8 把这条规则改了,改成如果字符串不是数字格式,就把数字转成字符串再比较,0 == "abc" 变成了 false。这是一个版本升级时容易漏掉的行为变化,如果项目里有依赖旧规则的校验逻辑,跨版本升级要格外注意测试这一块。
3. 权限校验里最容易踩的一种
1 | function checkRole($userRole) { |
这就是开头提到的那个真实案例的简化版。角色字段本来设计成整数,但接口没有做强校验,前端传了个非预期的字符串进来,== 比较把它当成 0,直接判定成游客权限——如果这条分支对应的是”降级到最低权限”还好,但如果反过来是”命中了某个高权限分支”,就是安全问题了。
改法很直接,能用 === 的地方全部用 ===,涉及类型不确定的输入,先做显式类型转换或者用 filter_var 做校验:
1 | if ($userRole === 0) { |
4. in_array 默认也是弱类型比较
1 | $allowedIds = [1, 2, 3]; |
in_array 和 array_search 默认都是用 == 做比较,跟单独写 == 一样的坑会在这里重现。这两个函数都有第三个参数 strict,传 true 就会改用 ===:
1 | in_array($value, $allowedIds, true); |
写涉及权限、金额、id 匹配这类校验逻辑的时候,in_array 后面不加 true 基本等于埋雷,这个已经变成我写代码时的条件反射,看到 in_array 就要确认第三个参数有没有传。
5. switch 语句同样是弱类型比较
1 | $input = "0"; |
switch 的 case 匹配用的也是 ==,如果 case 列表里同时有数字和字符串,容易匹配到不是你以为的那个分支。PHP 8.0 之后可以用 match 表达式替代,match 内部是严格比较(===),能从语法层面直接规避这类问题。
6. 小结
弱类型比较是 PHP 语言设计上的历史包袱,短期内不会消失,能做的就是养成习惯:涉及权限、金额、身份校验这类逻辑,一律用 ===,用到 in_array/array_search 记得带上 strict 参数,能升级到 PHP 8 的话优先考虑用 match 替代容易踩坑的 switch。