编程 PHP 8.6 批量弃用:31 项通过、4 项被否,先 grep 那几个保留标识符

2026-10-29 21:02:35

PHP 8.6 批量弃用:31 项通过、4 项被否,先 grep 那几个保留标识符

PHP 8.6 的 mass deprecation RFC(wiki.php.net/rfc/deprecations_php_8_6)投票已于 2026-08-10 13:00 UTC 关闭:35 张独立选票,每张需 2/3 多数。31 项通过,4 项被否。通过项在 8.6 抛 deprecation notice,计划在 PHP 9 移除。

现在没有任何东西会立刻坏掉,8.6 只是抛 notice,真正的工作是在 PHP 9 之前把受影响代码找出来。

五个新保留标识符

这是最容易影响用户代码的一类:

  • 用 let 作标识符(24 赞成 / 11 反对 / 9 弃权,68.6%)
  • 用 namespace 作类常量名(29 / 8 / 7,78.4%)
  • 用 is 作标识符(29 / 10 / 6,74.4%)
  • 把函数命名为 readonly(39 / 1 / 2,97.5%)
  • 用 _ 作常量和编译期别名(34 / 4 / 5,89.5%)

is 这一项有明确代价。Rowan Tommins 指出它与 Hamcrest 冲突:Mockery 依赖这个匹配库,README 里到处是 is(),Packagist 统计该包安装量超过 5 亿次。动议仍然过线,所以任何定义了 is() 的包要在 PHP 9 前改名。

// Hamcrest 风格的全局 is(),8.6 起 deprecation
function is($value) { /* ... */ }

函数与别名清理

多数接近全票:

  • is_double()、is_integer()、is_long()、doubleval(),约 95%
  • strcoll() 与 sort 系列的 SORT_LOCALE_STRING 标志,均 95%
  • metaphone()(20 / 8 / 16,71.4%)。Calvin Buckley 转述 Nora 的说明:RFC 里点名的替代品 Linguistics 包内部调用 php-src 自己的 metaphone(),所以它同样受影响,并不构成绕开弃用的办法。RFC 作者 Weilin Du 同意这段文字需要写清楚——该包只有一个 bridge 方法用到。
  • spl_classes() 100%,spl_object_hash()(26 / 9 / 11,74.3%)
  • SplFileObject 的 CSV 方法(25 / 5 / 15,83.3%)
  • mysqli::stmt_init() 与 mysqli_get_charset(),均 100%
  • define() 指定 $case_insensitive,100%
  • is_a() 与 is_subclass_of() 在 $allow_string 为 false 时传字符串,均 100%

还有一批「把对象当数组传」的弃用:array_walk() / array_walk_recursive() 的 $array、deflate_init() / inflate_init() 的 $options、zlib.inflate / zlib.deflate 流过滤器、bzip2.decompress / bzip2.compress 流过滤器、mb_convert_variables() 的 $vars、http_build_query() 的 $data。

http_build_query($request); // $request 是对象 → 8.6 弃用

其余分散的项:ReflectionProperty::setValue() / setRawValue() 传错类型、ReflectionMethod::invoke() / invokeArgs() 对静态方法传对象、ArrayIterator 继承 ArrayObject 实现的方法、session_set_save_handler() 传入不含 create_sid() / validateId() 的 handler 对象。

finally 里 return:行为改动,不是命名清理

在 finally 块里 return 以 39 / 3 / 4(90.7%)通过。它和上面那些改名不同——影响的是控制流,不只是报一条 notice。

function f(): int
{
    try {
        return 1;
    } finally {
        return 2; // 覆盖 try 的返回值
    }
}

这类代码不会因为一条 notice 就跑不动,但 PHP 9 移除后的语义要提前想清楚。

SplFileObject CSV 与 setCsvControl 的矛盾

投票末期,Takuya Aramaki 提出这项的内部矛盾:setCsvControl() 是配置 READ_CSV 所用分隔符 / 包围符 / 转义符的唯一途径,构造函数不接受这些参数。如果 PHP 9 移除 setCsvControl() 而 READ_CSV 仍在,READ_CSV 就永久锁死在默认值,制表符分隔文件无法再通过它读取。

他要求连同 READ_CSV 一起弃用,或者在给出替代之前保留 setCsvControl()。当时没有答复,该项以 25 : 5、15 弃权通过。

被否的 4 项

  • 弃用 list() 构造:23 赞成 / 23 反对 / 1 弃权,50.0%,未达 2/3。计票早期一度是 21:21,始终没拉开。
  • 把 in / out / inout 保留为标识符:8 / 21 / 15,27.6%。
  • 弃用 _ 作函数名及 gettext 的 _() 别名:10 / 22 / 11,31.3%。
  • 弃用 dechunk 过滤器:18 / 15 / 12,54.5%。Matteo Beccati 投反对,理由是会在 symfony/http-client 与 php-http/message 里触发 notice,且没给替代方案。Jakub Zelenka 补充:php-src 因为 HTTP stream wrapper 使用 chunked 编码,必须保留该过滤器,弃用用户态用法不减少任何代码和维护。

排查顺序

先查保留标识符:名为 is、let、readonly、namespace 的函数或类常量是静态分析一遍就能抓到的类型,误报可控。

# 1) 保留标识符:函数名与类常量名
grep -rnE 'function[[:space:]]+(is|let|readonly)[[:space:]]*\(' --include='*.php' .
grep -rnE 'const[[:space:]]+(let|namespace|_)[[:space:]]*=' --include='*.php' .
grep -rnE 'as[[:space:]]+_[[:space:]]*;' --include='*.php' .   # 编译期别名

# 2) 被弃用的函数
grep -rnE '\b(is_double|is_integer|is_long|doubleval|strcoll|metaphone|spl_classes|spl_object_hash|mysqli_get_charset)\s*\(' --include='*.php' .

# 3) 被弃用的方法
grep -rnE '->(stmt_init|setCsvControl|getCsvControl|setValue|setRawValue|invokeArgs)\s*\(' --include='*.php' .

# 4) define() 大小写不敏感
grep -rnE 'define\s*\([^;]*,[[:space:]]*true[[:space:]]*\)' --include='*.php' .

# 5) 多参数函数需要人工看实参
grep -rnE '\b(http_build_query|array_walk|array_walk_recursive|mb_convert_variables|is_a|is_subclass_of)\s*\(' --include='*.php' .

finally 里的 return 和「对象当数组传」这两类用 grep 只能定位候选,仍需人工确认控制流和实参类型。

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

推荐文章

程序员茄子在线接单