编程 PHP 8.6 里 trim() 开始吃掉 \f:默认字符集、array_filter 的 ValueError 与一批弃用

2026-11-03 21:02:16

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 / setFlags
  • spl_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 触发警告。

复制全文 生成海报 PHP 版本升级 兼容性

推荐文章

程序员茄子在线接单