?? 不是三元运算符的缩写,问了 deepseek 才知道叫 NULL合并运算符。之前也见过,但都没当回儿事,应该感觉跟 ?: 三元运算符差不多。这次碰着 bug 了,想要严肃的分析记录一下。

$equipment->atime = ($remote['atime'] && $remote['atime'] != '0000-00-00 00:00:00') ?? ($equipment->atime ?? $equipmentFields['atime']['default']);
# 一开始的写法
$equipment->atime = $remote['atime'] ?? ($equipment->atime ?? $equipmentFields['atime']['default']);

这里需要对设备日期更新做一个判断,如果传递过来的值不存在或者为空,就取当前设备的日期数据,最后不行就取默认值。

结果就是 orm 更新失败,通过 error_log 定位确认是 $equipment->save() 导致的。打开链接的 mysql 日志,重复调用找到执行的 sql,放到 navicat 工具里执行,确认是因为 atime 数据库字段为类型为 int(11),而传值为空字符串,类型不一致导致的失败。

将新增加的 && $remote['atime'] != '0000-00-00 00:00:00' 判断条件去掉,就没有报错。通过日志确认 $equipment->atime 为 1,而前面的 $remote['atime'] 未定义(被 unset 掉了)。

简化一下例子就是

> $arr = ['a'=>1, 'b'=>2];
> echo ($arr['c'] && $arr['c'] !='123') ?? 1;
PHP Notice:  Undefined index: c in php shell code on line 1

Notice: Undefined index: c in php shell code on line 1
> echo $arr['c'] ?? 1;
1
> echo @($arr['c'] && $arr['c'] !='123') ?? 1;

测试结果是这样的,但为什么未定义返回的是空呢,不应该是 1 吗?

为什么不是预期的 1
关键点在于:

1.$arr['c'] 返回 NULL(并触发警告)
2.NULL 在布尔上下文中被视为 false
3.false && ... 直接短路返回 false(不是 NULL)
4.?? 运算符只检查 NULL,false 不是 NULL,所以不触发默认值

而 false 在转换为字符串时,会成为空字符串。这就是 sql 里显示为 '' 的原因。
暂时还不清楚为什么 false 在 orm 更新赋值的过程中,变成了空字符串。

所以这里要么去掉 && 运算部分,要么改成三元运算符:

$equipment->atime = ($remote['atime'] && $remote['atime'] != '0000-00-00 00:00:00') ? $remote['atime'] :($equipment->atime ?? $equipmentFields['atime']['default']);