PHP 8 升级后 BIT(1) 标志位判断全部静默失效
某后台管理系统从 PHP 7 升级到 PHP 8 之后,有一个「设置默认项后自动同步到下游服务」的功能悄然失效——保存操作正常返回成功,数据库里的值也写对了,但下游就是收不到同步请求。加日志一看,触发同步的那段代码完全没有被执行到,而且没有任何报错。
现象
$row = Db::name('xxx')->where('id', $id)->find();
if ($row['is_default'] == 1) {
// 这段代码在 PHP 8 下永远不会执行
$this->syncToDownstream($row);
}数据库中 is_default 字段值确实是 1,var_dump($row['is_default']) 打出来却是 string(1) "\x01",而 var_dump($row['is_default'] == 1) 在 PHP 7 下是 true,PHP 8 下是 false。
根因
MySQL BIT(1) 类型通过 PDO 取回时,返回的不是整数,而是二进制字符串——1 对应 "\x01",0 对应 "\x00"。
PHP 7 的宽松比较对非数字字符串会尝试将其强转为整数再比较,"\x01" 被转成 1,所以 "\x01" == 1 为 true。
PHP 8 修改了这一行为(RFC: Saner string to number comparisons):当字符串不是数字字符串时,改为把整数转成字符串再比较,变成 "\x01" == "1",结果为 false。
更危险的是,BIT(1) = 0 时取回 "\x00",PHP 7 里 "\x00" == 0 居然也是 true(字符串转 int 得 0),意味着 PHP 7 下你甚至无法用 == 0 可靠地区分"是 0"还是"是空字符串"——这个坑在 PHP 7 时就已存在,只是多数人没察觉。
$val = $row['is_default']; // string(1) "\x01"
var_dump($val == 1); // PHP 7: true | PHP 8: false ← 静默坑
var_dump($val == 0); // PHP 7: true(!)| PHP 8: false
var_dump($val === 1); // false(类型不同,任何 PHP 版本都是 false)没有 Warning,没有 Notice,条件块直接被跳过。
常见受害字段:is_default、deleted、is_active、is_read、is_contract 等所有 BIT(1) 类型的布尔标志位。
正解
最稳妥的修法是在比较前用 intval() 显式转换:
// 错误 — PHP 8 下永远为 false
if ($row['is_default'] == 1) { ... }
// 正确 — 所有 PHP 版本均可用
if (intval($row['is_default']) === 1) { ... }也可以直接转 bool(对"非零即真"的场景更简洁):
if ((bool)$row['is_default']) { ... }如果能改 Schema,最彻底的方案是把 BIT(1) 改为 TINYINT(1),PDO 取回的就是真正的整数,不再有类型歧义:
ALTER TABLE your_table
MODIFY COLUMN is_default TINYINT(1) NOT NULL DEFAULT 0;- 应急修复:所有
BIT(1)比较改为intval($val) === 1 - 根治方案:表结构
BIT(1)→TINYINT(1),PDO 直接返回整数 - ThinkPHP 模型层:在
$type中配置字段转换('is_default' => 'integer'),可自动处理;但用Db::name()->find()原始查询不会自动转换
排查方法
升级 PHP 8 后如遇到「写入成功但下游/联动逻辑不触发」,先跑这段探针:
$val = $row['your_bit_field'];
var_dump(gettype($val)); // "string" → BIT 类型,有坑
var_dump(bin2hex($val)); // "01" 或 "00"
var_dump(intval($val)); // 1 或 0 — 这才是你想要的全库审计方式:搜索代码里所有 == 1 / == 0 的比较,对照 Schema 确认对应字段类型,凡是 BIT(n) 类型一律补 intval()。
一句话外卖
MySQL
BIT(1)经 PDO 永远是二进制字符串,PHP 8 宽松比较语义变更让所有== 1静默失效——升级前后必须用intval()包裹,或直接把列类型改为TINYINT(1)。