PHP 8.6 特性冻结倒计时:实用新特性与30项弃用清单
一、发布节奏:特性冻结就在这几天
PHP 8.6 的开发已经进入收尾阶段。根据官方发布计划:
| 里程碑 | 日期 |
|---|---|
| Beta 1(软特性冻结) | 2026 年 8 月 13 日 |
| Beta 3 | 2026 年 9 月 10 日 |
| 特性冻结(Feature Freeze) | 2026 年 9 月 22 日 |
| RC 1(硬冻结) | 2026 年 9 月 24 日 |
| RC 4 | 2026 年 11 月 5 日 |
| 正式发布(GA) | 2026 年 11 月 19 日 |
也就是说,9 月 22 日之后,没赶上这趟车的提案只能等 PHP 8.7。本版由 Daniel Scherzer、Matteo Beccati 和 Joe Ferguson 负责发布管理。
整体而言,8.6 是一个务实的版本:没有 JIT 级的大改动,没有属性钩子,也没有联合类型那样的语言重塑。它的看点集中在两处——几个立刻能用上的实用特性,以及一份规模罕见的弃用清单。
二、头号特性:部分函数应用(Partial Function Application)
这是 8.6 里讨论度最高的改动,投票结果是 33 票赞成、0 票反对——在 PHP internals 里几乎等于全票通过。
它的作用很直接:调用函数时留出占位符,PHP 返回一个闭包而不是立即求值。用 ? 标记单个待补参数,用 ... 吸收剩余的所有参数作为可变参数尾部。
过去要固定某个函数的某个参数,你得手写一个 fn() 箭头函数或者 function() use (...);现在这一整类样板代码可以直接省掉。生成出的闭包会保留原函数的参数名、类型和默认值,而且前置传入的参数是立即求值的,不是延迟到闭包被调用时才求值。
它补上了去年管道运算符留下的另一半。
三、其他确定落地的新特性
clamp() —— 终于进内核了
几乎每个项目都手写过 max($min, min($max, $value)),也几乎总有人把嵌套写反过。8.6 把它变成了原生函数:
clamp(101, min: 0, max: 100) // 返回 100
clamp("a", "x", "z") // 返回 "x"
它遵循 PHP 常规比较规则,因此不限于整数——字符串、DateTime 对象都能用。如果传入非法边界(比如最小值大于最大值,或者 NAN),会抛出 ValueError。
Time\Duration —— 让超时带上单位
PHP 8.6 引入 final readonly class Time\Duration(35 票赞成、1 票反对)。
这是一个看起来很小、实际影响深远的类。二十年来,PHP 的各类 API 接收超时参数时用的都是裸 int——毫秒?微秒?秒?只能靠文档和记忆。Duration 让超时参数第一次能携带自己的单位。
工厂方法覆盖了常见单位:fromNanoseconds()、fromMicroseconds()、fromMilliseconds()、fromSeconds()、fromMinutes()、fromHours(),以及 fromIso8601DurationString()。实例方法提供 add()、sub()、multiplyBy()、divideBy()、negate()、absolute() 和静态的 compare()。
内核中第一个使用它的是 IO\Poll\Context::wait(?Time\Duration $timeout)。可以预期,框架和客户端库会在一两个版本内跟进,在现有的 int/float 参数之外同时接受 Duration。
只读属性可以有默认值了
自 PHP 8.1 引入 readonly 属性以来,下面这行代码一直是致命错误:
Readonly property Foo::$name cannot have default value
8.6 开始它可以正常编译。该 RFC 于 2026 年 8 月 7 日通过,24 票赞成、0 票反对、5 票弃权,随后合并进 php-src。
这项改动不引入新语法、不改引擎内部表示、也没有破坏性变更,只是移除了一项编译期检查。但对插件系统、迁移执行器、规则注册表这类场景来说,它填补了一个长期不便的缺口——默认值会被视为初始化赋值,在构造函数体运行之前完成。此后的任何写入(包括构造函数内部)都算修改,会失败。
其余值得关注的改进
- 新的轮询 API:
IO\Poll\Context、StreamPollHandle、Event::Read,为底层 I/O 就绪检查提供支持 #[\.Override]支持类常量:8.3 给了属性,8.5 给了属性钩子,8.6 给了常量- 内置
SortDirection枚举:enum SortDirection { Ascending; Descending; },取代每个项目都会重新声明的 SORT_ASC 风格整数常量 - 更好的错误信息:错误会显示函数参数,
json_decode()会指出解析失败的具体位置 - 反射增强:属性上的
isReadable()与isWriteable(),且作用域感知 - 函数参数文档注释:可以为单个函数参数附加 doc comment
pack()/unpack()字节序修饰符:<与>现在可用于浮点数和有符号整数,处理二进制协议时可省掉手写的字节交换- 闭包优化:注意,原本提案有两半——自动推断
static与缓存无状态闭包,最终只有缓存这一半落地,static 推断因边界情况无法可靠识别而被作者主动放弃 - 此外还有
grapheme_strrev()、mysqli_quote_string()、Locale::getDisplayKeyword()、IntlNumberRangeFormatter、流的 TLS 会话恢复与 0-RTT 早期数据、Pdo\Pgsql::ATTR_CHUNK_SIZE等
四、必须留意的安全默认值变更
这是本版中最容易"静默出事"的一项——会话相关的 INI 默认值改了:
| 配置项 | 原默认值 | 8.6 新默认值 |
|---|---|---|
| session.use_strict_mode | 0 | 1 |
| session.cookie_httponly | 0 | 1 |
| session.cookie_samesite | 不设置 | "Lax" |
方向是对的,安全默认值收紧是好事。但它确实会打断一部分现有部署:跨站 POST 流程,以及任何通过 JavaScript 读取会话 cookie 的代码,都会停止工作。
建议做法:在 php.ini 里显式固定这三项,让升级过程无法在背后改动它们,然后再有意识地逐项调整。
五、真正的重头戏:约 30 项弃用
如果只读一段,就读这段。8.6 的弃用清单是为 PHP 9.0 铺路的路线图。
1. mb_ereg* 进入倒计时(唯一的真移植项目)
mbstring 的 mb_ereg 系列背后的正则引擎 Oniguruma,已于 2025 年 4 月 24 日停止上游维护。PHP 的回应是:8.6 弃用,PHP 9.0 移除。
涉及 mb_ereg()、mb_eregi()、mb_ereg_replace()、mb_regex_encoding() 及其同族函数。应迁移到 PCRE,即使用带 u 修饰符的 preg_*。
需要为它留足预算——这是语义迁移,不是重命名。两个引擎在足够多的细节上存在分歧,每迁移一个模式都应配套测试。Drupal 已经为此开了 issue,一定程度上说明了它的普遍程度。
2. "对象当数组用"的时代终结
以下函数不再接受对象作为输入:array_walk()、array_walk_recursive()、deflate_init()、inflate_init()、zlib 与 bzip2 的流过滤器参数、mb_convert_variables()、http_build_query()。
修复几乎总是在调用处加 get_object_vars($obj)。
3. 关键字正在被收回
为了让未来的语法有词可用,PHP 必须先弃用这些词的 userland 用法,等一两个版本再正式保留:
readonly作为函数名(39 票对 1 票)is与let作为标识符(is被留作未来模式匹配之用)namespace作为类常量名_作为常量
4. 其他会影响日常的弃用
- 从
__construct()或__destruct()返回值被弃用;从finally块中返回同样如此(39 票对 3 票,是整个弃用系列中票数最悬殊的一项) trim()、ltrim()、rtrim()、chop()现在默认剥离换页符\f——如果解析定宽文本,这是静默的输出变化- 一批过去只发警告的函数改为抛异常:
array_filter()的$mode、GMP 的移位与幂运算、pcntl_alarm()/pcntl_exec()、Phar::mungServer()、curl 的CURLOPT_READFUNCTION返回值、不可字符串化的 intl 时区、SimpleXMLElement与会话 cookie 设置中的 NUL 字节 preg_grep()出错时返回false,而不是部分数组——检查一下你对返回值的处理is_a()与is_subclass_of()以字符串作为第一个参数被弃用ArrayIterator中继承而来却毫无意义的方法、SplFileObject的 CSV 方法、不完整的session_set_save_handler()实现被弃用- DOM 的只读属性改用
public private(set)——写入会报错,而不是静默失败
好消息是:8.6 本身不会有什么东西坏掉。被弃用的函数仍然正常工作,只是开始发出警告。这正是团队往往低估的地方。
六、没能上车的提案
一些被投票否决的提案同样值得知道,避免被过时的文章误导:
- 弃用
list():23 票对 23 票平局。PHP 的语言变更需要三分之二多数,平局不是"差一点",而是失败 - 管道赋值运算符:被否决
let构造 / 块级作用域:被否决,但let仍被保留为关键字- 绑定擦除的泛型类型:被否决——泛型仍是 PHP 长期悬而未决的问题
__exists()魔术方法、array_only_keys()/array_except_keys()、array_path_get()/array_path_exists()、前缀/后缀字符串函数:均被否决
仍在讨论中、可能顺延到 8.7 的有:函数自动加载(标记 5)、BackedEnum::values();ReflectionAttribute::getCurrent() 在 beta 阶段仍在投票,依赖它之前应确认最终状态。
七、现在该做什么
距离 GA(11 月 19 日)还有两个月,大多数团队有充足的缓冲期。但有几件事现在做成本最低:
- 如果你维护库:这周就把 PHP 8.6 的 beta 版本对你的测试套件跑一遍。Beta 按设计是不稳定的,但这正是你的 bug 报告还能改变点什么的窗口——RC1 在 9 月 24 日封锁分支之后就没机会了
- 如果你维护应用:把弃用清单对着自己的代码库读一遍,尤其是任何可以追溯到 PHP 5 或早期 PHP 7 的部分。在 11 月发现一个十年前的辅助函数踩了弃用项,总比在升级之后发现要好
- 检查会话语义:确认跨站 POST 流程与前端读取 cookie 的逻辑,提前在
php.ini显式固定那三项配置 - 评估 mb_ereg 使用面:这是本版唯一真正的移植工程,越早排期越好
PHP 8.6 不是一次激进的底层重写,而是一门语言在被有意维护:保留关键字以备未来语法、剥离失去上游维护的正则引擎、终结"对象当数组用"的时代、把警告变成异常、收紧安全默认值。对现在就读懂这些信号的人来说,PHP 9.0 会是一次容易得多的升级。