返回博客

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 字段值确实是 1var_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" == 1true

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 时就已存在,只是多数人没察觉。

坑:BIT(1) 经 PDO 取回是 \\x00/\\x01,PHP 8 宽松比较彻底失效
$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_defaultdeletedis_activeis_readis_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;
正解:intval() 包一层,或 Schema 改成 TINYINT(1)
  • 应急修复:所有 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)