load average 统计的是"正在运行 + 等待 I/O"的进程数,不是 CPU 使用率。8 核机器 load 8 是满载而非危险,load 高也可能是磁盘 I/O 阻塞——必须配合 vmstat 的 r/b 两列定性。本文讲透正确读法、容器环境陷阱和生产告警配置。
先给结论: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]:您的订单已发货
几个必须知道的细节:
- 返回的是保留类型信息的闭包——
ReflectionFunction拿到的是原函数的签名,参数名、类型声明、默认值都完整保留,静态分析不会失效 - 占位符是位置占位,
?必须出现在与原函数签名匹配的位置上 - 每个
?在生成的闭包上都是必传参数,即使原函数那个参数是可选的 - 预先传入的参数在创建偏函数时求值,不是调用闭包时才求值——这条和闭包的直觉不同,踩过就知道
- 保留迟静态绑定:在继承链上
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\Instant、Time\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.cookie、session_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 更适合"固定部分参数、复用同一函数"的场景。