代码 PHP 8.6 的 trim() 默认多剥一个 \f:定宽文本和分页流会静默错位

2026-11-09 21:04:53

PHP 8.6 的 trim() 默认多剥一个 \f:定宽文本和分页流会静默错位

PHP 8.6 对 trim() 家族的默认字符集做了一处改动:trim()、ltrim()、rtrim()、chop() 现在默认也会去掉换页符 \f(0x0C)。改动没有 deprecation,没有警告,只是返回的字符串短了一个字节。

参考:PHP 8.6 迁移说明、php.watch/versions/8.6。

默认字符集从 6 个变成 7 个

在 8.6 之前,这几个函数不带第二个参数时,剥的是固定的 6 个字符:

" "    空格 0x20
"\t"   制表符 0x09
"\n"   换行 0x0A
"\r"   回车 0x0D
"\0"   NUL 0x00
"\x0B" 垂直制表符 0x0B

8.6 在这个集合里补上了 \f(换页符 0x0C)。行为差异只体现在字符串首尾恰好有 \f 的时候——中间出现的 \f 不受影响。

为什么叫静默变化

函数签名没变,返回值类型没变,不会抛异常也不会记日志。调用方只看到字符串长度变了:

  • 靠列偏移解析定宽记录的代码,首尾少一个字节就可能整体错位;
  • 先 trim() 再按字符数 substr() 或 str_pad() 的代码,算出来的宽度和实际不一致;
  • 把 trim() 结果做哈希、做缓存键、写进数据库的,值变了但没有痕迹。

这类问题不会在 CI 里报错,只会在某几行数据上出现“看起来少了一列”的现象。

典型出问题的场景

  1. 打印/票据文件。很多报表流用 \f 作为分页分隔,一段内容末尾带 \f 送进 trim(),分页符就没了,打印机不再换页。
  2. 老式 COBOL 风格定宽导出。记录按固定列对齐,末尾填充字节里可能就含 \f,剥掉后整条记录的列宽对不上。
  3. 从 PDF 或终端抓下来的文本。PDF 文本提取和终端截屏里 \f 很常见,清洗时常被一并 trim() 掉。
  4. 日志里用 \f 分隔保留块。把 \f 当块边界符的日志格式,trim() 之后块边界消失,解析器拿不到分块点。

复现与验证

// PHP 8.6 之前:int(3)
// PHP 8.6:     int(3) —— 尾部 \f 被剥掉后是 "abc"
var_dump(strlen(trim("abc\f")));

// 看清楚到底剥了什么
var_dump(bin2hex(trim("abc\f")));   // 8.6: "616263"
var_dump(bin2hex("abc\f"));         // 始终: "6162630c"

// 左侧同理
var_dump(bin2hex(ltrim("\fabc")));  // 8.6: "616263"
var_dump(bin2hex(rtrim("abc\f")));  // 8.6: "616263"
var_dump(bin2hex(chop("abc\f")));   // 8.6: "616263"

升级前建议在旧版本上跑一遍上面几行,升级后再跑一遍对比 bin2hex 的输出。

修复:显式写第二个参数

要保留旧行为,把字符集写全,不要依赖默认值:

$s = trim($s, " \t\n\r\0\x0B");   // 8.6 之前的行为,不含 \f

反过来,如果确实想剥 \f,也同样显式写出来,别指望默认值:

$s = trim($s, " \t\n\r\0\x0B\f"); // 明确包含换页符

ltrim()、rtrim()、chop() 同理。对于解析定宽数据的代码,这一步基本是必须的——默认字符集本来就不该是隐式契约。

同一版本里其余需要回归的改动

8.6 还有一批“从警告变异常”的调整,升级后要跑一遍回归:

  • array_filter() 的 $mode 传非法值时改为抛 ValueError;
  • GMP 的移位与幂运算、pcntl_alarm()/pcntl_exec()、Phar::mungServer()、CURLOPT_READFUNCTION 的返回值、不可字符串化的 intl 时区、SimpleXMLElement 与会话 cookie 设置中的 NUL 字节,都从警告改为抛异常;
  • preg_grep() 出错时返回 false,不再返回部分数组;
  • DOM 的只读属性改用 public private(set)。

PHP 8.6 计划于 2026-11-19 发布。搜索代码库里的 trim(、ltrim(、rtrim(、chop(,把没有第二个参数的调用逐个确认一遍,是这次升级里成本最低、收益最直接的一项检查。

复制全文 生成海报 PHP 字符串处理 版本升级 踩坑

推荐文章

程序员茄子在线接单