PHP 8.6 把 array_intersect() 的字符串转换提前到扫描阶段
array_intersect() 一直按字符串比较值:把元素转成 string 再比较。8.6 改的是转换的时机——从“排序比较时”提前到“扫描输入时”。这条变更记录在 php/doc-en issue #5901 的 Backward Incompatible Changes 节。
变化点
旧实现把值转换推迟到排序比较阶段;PHP 8.6 在扫描输入数组的过程中就完成转换。受影响的不只是性能,而是转换发生的次数、顺序和产生副作用的位置。
结果会变的几种情况
有状态的 __toString()
array_intersect() 只接受可转字符串的标量,或实现了 __toString() 的对象。如果 __toString() 带内部状态(计数器、懒加载、缓存填充),每次调用返回的字符串可能不同:
final class Token
{
private int $n = 0;
public function __construct(private string $v) {}
public function __toString(): string
{
return $this->v . (++$this->n > 1 ? '#' : '');
}
}
array_intersect([new Token('a'), new Token('b')], ['a', 'b']);
转换提前后,调用 __toString() 的时机与调用序列都可能与旧版不同,因此这类实现的返回值也就可能不同。文档把这一点明确列为结果可能改变的情形。
__toString() 抛异常时,触达的是哪一个
如果某个元素的 __toString() 抛出异常,实际被触达的那个异常取决于转换按什么顺序发生。扫描输入时按输入顺序转换,谁先被扫描到,谁的异常先抛出;旧实现由排序比较路径决定。同一个输入,抛出的异常可能来自不同元素。
转换告警的数量和顺序
元素本身是数组时会触发 Array to string conversion 警告。转换从排序阶段前移到扫描阶段,告警的条数和先后顺序都会变化。任何依赖告警数量或顺序的测试与日志断言,都需要重新确认。
空数组与类型校验的顺序
两条顺序约束:
- 参数类型校验发生在检查空数组、转换值之前。传入非 array 参数时先抛
TypeError,不会因为另一个参数是空数组而短路跳过校验。 - 任一输入数组为空时,不做值转换。此时既不会调用
__toString(),也不会产生转换告警——这会让依赖__toString()副作用的代码(计数、写日志、触发懒加载)静默地少执行一次。
自查清单
- grep 所有
array_intersect(调用,确认第一个之后的参数里是否含实现了__toString()的对象,尤其是带状态或会抛异常的对象。 - 检查元素是否可能是数组(嵌套数据结构直接进入比较),核对现有测试对
Array to string conversion告警数量、顺序的断言。 - 检查是否有
try/catch依赖异常来自哪个元素,或依赖异常消息中的具体值。 - 检查空数组输入路径:8.6 下不转换值,
__toString()的副作用不再发生。
同批 Backward Incompatible Changes
同一节还列出(不展开):
- by-reference
foreach在数组压缩后可能访问之前被跳过的元素 ??/empty()对 magic property:__isset()已写入属性表后不再调__get()trim()默认剥离字符集加入 form feedfclose()/file_put_contents()/copy()在 flush/close 失败时返回false
时间线与链接
PHP 8.6 RC5 计划于 2026-11-05,GA 2026-11-19。
- 变更条目:
- 函数文档: