先给结论:PHP 8.6 是近年最值得升级的一个版本,因为它终于补上了"函数复用"这块语法短板——部分函数应用(PFA)让你用 ? 占位符预先绑定参数、直接得到一个闭包,和 PHP 8.5 的管道操作符 |> 组合后,数据处理终于能像写 Linux 管道一样顺。但升级前必须先看两件事:Session 三个默认值做了安全向变更(Laravel 项目基本免疫,裸用原生 Session 的要自查),以及 26 项弃用mbstring 正则函数因 oniguruma 停止维护被弃用——这条影响面最广。正式版 2026 年 11 月 19 日发布,今天(9 月 22 日)刚过功能冻结,9 月 24 日出 RC1。

一、先看时间线,确认你的升级窗口

节点 日期 状态
Alpha 1 ~ 3 2026-07-02 ~ 07-30 ✅ 已完成
Beta 1(软特性冻结) 2026-08-13 ✅ 已完成
Beta 3 2026-09-10 ✅ 已完成
功能冻结 2026-09-22 今天
RC 1 2026-09-24 即将发布
RC 4 2026-11-05 ——
正式版 GA 2026-11-19 还有约两个月

版本负责人是 Daniel Scherzer、Matteo Beccati、Joe Ferguson。现在想提前试,docker pull php:8.6.0beta2-cli 就能跑,别上生产。

二、门面特性:部分函数应用(PFA)

这是 8.6 的旗舰特性。场景很常见:你有一个多参数函数,只想固定前面几个参数、把剩下的留到调用时给。以前只有两条路,都别扭:

// 老写法一:包一层闭包
$smsSender = fn(int $uid, string $msg) => sendNotification('SMS', $uid, $msg);

// 老写法二:First-Class Callable 套一层
$smsSender = sendNotification(...);   // 但一个参数都固定不了

8.6 引入 ? 占位符之后:

$smsSender = sendNotification('SMS', ?, ?);
$smsSender(1001, '您的订单已发货');
// 输出:通过 [SMS] 发送给用户 [1001]:您的订单已发货

几个必须知道的细节:

  1. 返回的是保留类型信息的闭包——ReflectionFunction 拿到的是原函数的签名,参数名、类型声明、默认值都完整保留,静态分析不会失效
  2. 占位符是位置占位? 必须出现在与原函数签名匹配的位置上
  3. 每个 ? 在生成的闭包上都是必传参数,即使原函数那个参数是可选的
  4. 预先传入的参数在创建偏函数时求值,不是调用闭包时才求值——这条和闭包的直觉不同,踩过就知道
  5. 保留迟静态绑定:在继承链上 Parent::method(?)Child::method(?) 调出来的不是同一个方法

这个 RFC 的 v1 版本在 2021 年被否决过,v2 以 33 票赞成、0 票反对全票通过——算是 PHP 社区罕见的共识。

三、真正改变手感的是 PFA + 管道操作符的组合

单独看 PFA 只是语法糖,但配上 PHP 8.5 的管道操作符 |> 就不一样了:

$slug = 'Hello World'
    |> str_replace(' ', '-', ?)
    |> strtolower(...);

// "hello-world"

以前写这条数据流,你要么套两层嵌套函数(可读性全毁),要么定义一堆中间变量。现在从左到右读就是执行顺序。这是 PHP 语法层面上少有的一次"手感升级",对写数据清洗、字符串处理、集合变换的代码收益最明显。

四、三个日常能用上的新东西

1. clamp() —— 终于不用再手写 min(max())

clamp(10, min: 0, max: 100);  // 10
clamp(101, min: 0, max: 100); // 100
clamp(-1, min: 0, max: 100);  // 0

细节:min > max 直接抛 ValueError(比 min(max()) 默默给出错误值安全得多);支持所有可比较类型,包括字符串和 DateTime。建议只用数值和时间,字符串比较走的是 PHP 松散比较规则,clamp('P', 'A', 'Z') 能跑但同事会看懵。

2. Time\Duration —— 纳秒精度的时长类

use Time\Duration;

sleep(Duration::fromMilliseconds(500));

$base  = Duration::fromMilliseconds(100);
$delay = $base->multiplyBy(2 ** $attempt);   // 重试退避
if ($total > $delay) { /* 可直接用比较运算符 */ }

final readonly 不可变,支持 ISO 8601 时长字符串构造。这不是孤立的工具类——它是 PHP 新时间库的第一块砖,后续会补 Time\InstantTime\CalendarInterval以后所有"时间量" API 都会收口到这套新类上,现在新写的代码用 Duration::fromSeconds($sec) 替代裸 sleep($sec),能省掉未来一次重构。

3. 只读属性可以有默认值了

8.6 之前这是编译错误,导致实现只读接口属性时给固定默认值非常别扭:

final readonly class CreateBooksTable implements Migration
{
    public string $name = '2026_01_01_create_books_table';
}

只读语义没变——初始化后依旧不能重新赋值,只是移除了语法限制。

另外还有:函数参数可直接写行内文档注释(ReflectionParameter::getDocComment() 可读取,IDE 和 Psalm/PHPStan 直接受益)、全局 SortDirection 枚举、枚举可实现 __debugInfo()、流操作统一错误模型(error_mode 支持抛出异常模式,StreamErrorCode 定义 50+ 语义化错误码)、UriBuilder 构建器。

五、底层大件:Io\Poll 轮询 API

这部分短期跟你写业务无关,但长期会改变 PHP 生态的网络层性能。

Io\Poll 是替代 stream_select() 的跨平台统一 IO 多路复用接口:Linux 用 epoll、macOS/BSD 用 kqueue、Solaris 用 event ports、Windows 用 WSAPoll。stream_select() 在 1000+ 连接规模下线性变慢,epoll 是 O(1)——ReactPHP、Amp、Revolt、FrankenPHP 这些异步运行时都会陆续切到它上面。

use Io\Poll\{Context, Event, StreamPollHandle};
use Time\Duration;

$poll   = new Context();
$server = stream_socket_server('tcp://0.0.0.0:8080');
stream_set_blocking($server, false);
$poll->add(new StreamPollHandle($server), [Event::Read], ['type' => 'server']);

while (true) {
    foreach ($poll->wait(new Duration::fromSeconds(1)) as $watcher) {
        if ($watcher->hasTriggered(Event::Read)) {
            // accept 新连接
        }
    }
}

没有事件循环、没有回调、没有 Promise——它就是"我要等哪些 fd 准备好"的极简原语,封装是上层框架的事。

六、升级风险:Session 默认值 + 26 项弃用

Session 三个默认值改了(仅影响全新安装,不会自动改变已有 php.ini):

配置项 旧默认 新默认 谁会踩坑
session.use_strict_mode 0 1 跨子域共享 session_id、SSO 票据交接、URL 传 SID 的应用
session.cookie_httponly 0 1 用 JS 读 cookie 拿会话标识的老代码
session.cookie_samesite 未设置 Lax 跨站 POST 携带 session cookie(如 SAML SSO 回调、iframe 回调)

Laravel 项目基本免疫——Laravel 自己接管 Session Cookie 的生成与配置,不依赖这几个 ini 默认值。裸用原生 Session 的项目要按这三步自查:php -i | grep session 记录当前配置 → 代码里搜 document.cookiesession_id() 等用法 → 灰度跑一周重点验证 SSO 回调和跨子域场景。

弃用清单里最需要注意的是 mbstring 正则函数——底层 oniguruma 库停止维护,mb_ereg_* 系列进入弃用流程。中文项目里这个用得不少,需要在 8.6 正式版之前替换掉。其余大多是改个函数名的事:

弃用 替代
is_double() / is_integer() / is_long() is_float() / is_int()
doubleval() floatval()
metaphone() 用户态实现(如 noodlesnz/double-metaphone

按官方说明,这些弃用在 8.7、9.0 会变成硬错误——现在改的成本,是 11 月之后改的五分之一

FAQ

Q:PHP 8.6 什么时候能用上正式版? 2026 年 11 月 19 日 GA。当前(9 月 22 日)刚过功能冻结,9 月 24 日出 RC1,11 月 5 日出 RC4。想提前验证可以跑 docker pull php:8.6.0beta2-cli 或等 RC 版本。

Q:Laravel 项目升级 8.6 风险大吗? 风险可控。Session 默认值的三项变更对 Laravel 基本无影响(框架自管 Cookie 配置);主要工作是清理弃用 API,尤其是 mb_ereg_* 系列和 is_integer/is_double 这类别名函数。建议在 8.6 RC 阶段用 PHPUnit 跑一遍回归。

Q:部分函数应用(PFA)和闭包有什么区别? PFA 直接由原函数生成,保留完整的参数名、类型声明和默认值,反射信息不丢失,也不用写 use 导入变量;闭包是手写的匿名函数,类型信息靠你自己标注。PFA 更适合"固定部分参数、复用同一函数"的场景。

艾林博客 - 技术分享、开发经验与AI探索的个人技术博客
艾林博客 - 技术分享、开发经验与AI探索的个人技术博客

延伸阅读: