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 只能定位候选,仍需人工确认控制流和实参类型。