PHP 8.6 的三处默认行为收紧:trim 吃掉 \f、array_filter 的非法 $mode 变成 ValueError、ArrayIterator 一批方法弃用
PHP 8.6(2026-11-19 发布)里有几处改动不动函数签名,但会改运行结果。下面三处最容易在升级后才被发现:一处扩了默认字符集,一处把静默容错换成了异常,一处开始产出弃用告警。
trim / ltrim / rtrim / chop:默认字符集多了 form feed
改之前,这四个函数(chop() 是 rtrim() 的别名)默认剥掉的字符集是:
" \t\n\r\0\x0B" // 0x20 0x09 0x0A 0x0D 0x00 0x0B
8.6 起加入 form feed,也就是 "\f"、0x0C。同一段输入,前后两个版本的差别:
// PHP 8.5
bin2hex(trim("\x0cfoo\x0c")); // "0c666f6f0c" 两侧的 \f 都还在
rtrim("tail\x0c"); // "tail\x0c"
// PHP 8.6
bin2hex(trim("\x0cfoo\x0c")); // "666f6f" 两侧的 \f 被剥掉
rtrim("tail\x0c"); // "tail"
从字节层面看,一行末尾带分页符的 CSV:
00000000: 41 6c 69 63 65 2c 74 61 69 6c 0c 0a Alice,tail..
fgetcsv() 读出来的第二个字段是 "tail\x0c"。8.5 里 trim() 之后它还是 5 个字节;8.6 里变成 4 个字节的 "tail"。没报错,没告警,只是值变了。
容易踩的地方基本集中在 0x0C 会被当成排版字符的场景:
- CSV / 表格导入:从 PDF、报表工具、打印机导出的文本习惯用
\f分页,分页符会落在行尾或字段里。以前字段带着它,$row[1] === 'tail'判不出来;升级后被静默剥掉,比较反而"对上了",或者反过来——字段长度、md5 校验、导回原格式时的字节全都对不上。 - 从 PDF / 打印机抓的文本:一页结束时插入一个
\f,按行切开后首尾各挂一个,正好落在trim()的处理范围内。 - 用
\f拼出来的日志:把\f当分段分隔符,trim()之后分隔符消失,两段日志粘成一段。
修法是别依赖默认值,第二个参数显式给全:
$clean = trim($raw, " \t\n\r\0\x0B");
必须用双引号,\0、\t 这些才会转义;写 "\x20\t\n\r\x00\x0B" 是等价的。字符顺序无所谓,但漏掉任何一个都会改变行为。确实需要连 \f 一起剥,就把它显式加进去:" \t\n\r\0\x0B\x0C"。
array_filter:非法 $mode 不再被当作 0
$mode 的合法取值只有三个:0(只传 value,默认)、ARRAY_FILTER_USE_KEY、ARRAY_FILTER_USE_BOTH。8.5 及以前传入其他值会被静默转成 0 按默认路径跑;8.6 抛 ValueError。
出问题的典型来源是配置、环境变量或 JSON 里的字符串:
// $mode 来自 env / 配置文件,可能是 "key"、"both"、true
array_filter($data, $fn, $mode);
"both" 转成 int 是 0,true 转成 1(正好撞上 ARRAY_FILTER_USE_BOTH)。过去这类错误一半被吞掉,一半变成"过滤结果少了几项"的悬案,现在会直接在调用点炸出来。
修法是在传进去之前把外部值收敛成白名单:
$mode = match (strtolower((string) $rawMode)) {
'value' => 0,
'key' => ARRAY_FILTER_USE_KEY,
'both' => ARRAY_FILTER_USE_BOTH,
default => throw new InvalidArgumentException("bad filter mode: {$rawMode}"),
};
array_filter($data, $fn, $mode);
ArrayIterator 一组方法与 spl 系列弃用
8.6.0 起下列符号标记弃用:
ArrayIterator::asort/ksort/natcasesort/natsort/uasort/uksort/serialize/unserialize/getFlags/setFlagsspl_object_hash()spl_classes()SplFileObject::fgetcsv/fputcsv/getCsvControl/setCsvControl
$it = new ArrayIterator([3, 1, 2]);
$it->asort(); // 8.6.0 起触发 E_DEPRECATED
spl_object_hash($obj); // 同上
这类改动不会立刻改变行为,只是开始刷 E_DEPRECATED。升级后先用 error_reporting(E_ALL) 跑一遍测试,或者把 deprecation 提成异常,能把调用点一次性捞干净。
同一版本里另外两处返回值与消息的变化
ini_get_all()返回的数组里新增builtin_default_value元素。按位置遍历或对返回数组做结构断言的代码要跟着调。json_last_error_msg()和JsonException的消息里带上了错误位置。错误信息文案会变,别拿它做字符串匹配断言,改用错误码。
不属于本篇范围的改动
8.6 同时还有:partial function application、Time\Duration、clamp()、SortDirection、session 默认值安全化(use_strict_mode=1、cookie_httponly=1、cookie_samesite=Lax)、Io\Poll、Stream 错误 API、TLS session resumption、mb_ereg 系列弃用,以及构造函数里 return 触发警告。