照例扫一眼新特性清单本来就能交差。但把 RFC 投票记录从头读到尾之后,我把升级前的功课改了——因为两件事和网上说的都不一样:8.6 的头号性能卖点在 8 月 11 日被撤回了半个;而到处流传的那份弃用清单里,有四项一票都没过。

一、闭包优化:24 票赞成,落地一半

8.6 的性能卖点是 Ilija Tovilo 的闭包优化 RFC。2026 年 1 月 30 日提出,3 月 13 日投票结束,24 赞成、0 反对、1 弃权——这是整个 8.6 投票季最漂亮的票数。

它本来是两件事打包的。

第一件:static 自动推断。写在方法里的闭包会隐式捕获 $this,哪怕函数体里压根没用到它。于是对象持有闭包、闭包持有对象,形成一个只有循环收集器能解开的引用环——而循环收集器经常在整个请求结束前一次都不跑。8.6 打算让编译器自己判断"这个闭包没碰 $this",替你把 static 标上。

第二件:无状态闭包缓存。一个 static、不捕获任何变量、不声明 static 变量的闭包,逻辑上每次创建出来都是同一个东西,那就没必要每次都真的 new 一个。

第一件没了。

2026 年 8 月 11 日,Tovilo 发了一份 errata,把 static 推断撤了。最终进 8.6 的只有第二件。

想自己验也简单,起个 8.6 容器跑一行:

$closure = function () { return 1; };
$ref = new ReflectionFunction($closure);
var_dump($ref->isStatic()); // false

函数体里没有 $this,但 8.6 不会把它推断成 static。

顺带说一句,这个 false 我没在 8.6 上亲手跑出来,是从那份 errata 和几篇 8.6 实测文章里对上的。想较真的话起个 RC 容器跑一遍,很快。

二、撤掉它的那个反例,值得单独看一眼

推断规则本来就够保守了,七条:不用 $this、不用 $$var(因为变量里可能存着字符串 this)、不做 Foo::bar() 式的调用、不做 $f() 式调用、不用 call_user_func()、不在内部再声明非 static 闭包、不用 require/include/eval。

规则有效——Tovilo 在 Symfony Demo 上做过测试,把代码库里所有手写的 static 全删掉,编译器把 87 个里的 68 个重新标了回来,约 78%。

杀死它的是一条比这七条都脏的路径:

class Foo {
    public function instanceCall() { return $this; }
    public function test($c) {
        return (function () use ($c) { return array_map($c, [1]); })();
    }
}
var_dump((new Foo)->test('Foo::instanceCall'));
// array(1) { [0] => object(Foo)#1 }

闭包体里一行 $this 都没有。是 array_map() 从上一个栈帧把实例递了进去。数组 callable ['Foo', 'instanceCall'] 一个道理。

Tovilo 的立场是:这种隐式行为应该先走一遍弃用、再移除,而不是让引擎直接假定没人依赖它。

我同意这个判断。但也得承认它的代价:一个能绕过七条规则的边缘情况,永久性地拖住了一项对所有人都有收益的优化。 PHP 的向后兼容承诺在这儿是硬的——宁可收益不要,也不能让任何一行代码悄悄变语义。这就是 PHP。

三、于是那个 3% 变成了什么

现在网上关于 8.6 性能的数字有三层,混着看一定会看错。

80% 是合成基准:一个循环里创建一千万个完全相同的闭包。这种数字只在"优化确实生效了"这个意义上有价值,放进你的应用里等于零。

3% 是 Laravel 模板渲染场景:3637 次闭包实例化里能省掉 2384 次。这个数字是真的,但它是两半加在一起跑出来的,而且前提是代码里的闭包都被标成了 static。

12% 是编的。我搜到不止一个站写着"WordPress 场景比 8.3 提升约 12%",同一个页面还把 typed class constants 和 json_validate() 当成 8.6 新特性——那两个是 8.3 的东西。这种站看到就绕开。

推断没了之后,缓存这一半的触发条件变严了,三个条件必须同时成立:

// 命中缓存:static、不捕获、不声明 static 变量
$fmt = static fn (int $cents): string => number_format($cents / 100, 2);

// 不命中:捕获了 $rate
$fmt = static fn (int $cents): float => $cents * $rate;

// 不命中:声明了静态变量
$fmt = static function (): int { static $calls = 0; return ++$calls; };

// 不命中,而且在方法里还会把 $this 一起拖着
$fmt = fn (int $cents): string => number_format($cents / 100, 2);

最后那个是绝大多数 PHP 代码的真实写法。

所以 3% 不是升级白送的,是你要自己写那五个字符换来的。

四、顺带一个 BC break

同一词法位置的两个无状态闭包,现在是同一个对象:

function make(): Closure { return static fn () => 42; }
$a = make();
$b = make();
var_dump($a === $b); // true

以前是 false。JavaScript 明确规定这种写法必须返回 false——每次对函数表达式求值都产生一个新对象。RFC 把这条列进了三个已接受的向后不兼容变更之一,上 Hacker News 的时候评论区就围着这个吵。

一级 callable 也一起被缓存了,hello(...) === hello(...) 现在同样是 true。

真会咬到人的概率极低。要说风险点,是 ReflectionFunction::getClosureThis()——如果你有代码靠它判断闭包绑没绑对象,行为会变。

五、35 张选票,4 项没过

弃用 RFC 是 35 个独立投票,每一项单独需要三分之二,2026 年 8 月 10 日 13:00 UTC 一起截止。最终 31 过 4 否。

提案 赞成 : 反对 结果
弃用 list() 构造 23 : 23 否决,list() 保住
保留 in / out / inout 为关键字 8 : 21 否决
弃用 _()(gettext 别名) 10 : 22 否决
弃用 dechunk 过滤器 18 : 15 否决,54.5%

(我上一篇写 8.6 的时候说的是 26 项弃用,那会儿只数了弃用项;35 是把这批 ballot 全算上,里面还有保留关键字这类。)

list() 那一项最戏剧:从 21:21 走到 23:22,最后停在 23:23 平票,离三分之二差得远。它差一点就成了 PHP 9 的一桩大工程。

但你现在去搜「PHP 8.6 弃用 list」,能搜到一堆。这些稿子写的是提案内容,不是投票结果。 进了 RFC 不等于进了语言——这两件事在 PHP 中间隔着一次三分之二投票。

险胜的也有:保留 let 一度正好压在三分之二线上,最后 24:11 过线;保留 is 顶着 Hamcrest 那个被 Mockery 依赖、Packagist 安装量五亿多的 is() 函数,还是 29:10 过了。Rowan Tommins 那句"我们得想清楚值不值得打扰这么多代码",没拦住。

六、内核在守的那条线

把通过的 31 项和否决的放一起看,立场相当清楚。

语言底层——保留关键字、闭包语义、类型——基本给过。数组便利函数一律不给过:array_only_keys()、array_except_keys()、array_path_get()、array_path_exists(),全否。__exists() 那个用来区分「键不存在」和「键存在但值是 null」的魔术方法,也否了。

社区里那句总结说得挺准:你要 array_except_keys(),那是 Laravel 的职责,不是引擎的职责。

管道赋值操作符 |>= 也没过,14 赞成 12 反对 7 弃权,53.8%,够不上三分之二。它一路从打平爬到 53.8%,就是差一口气。

还有一场更可惜的:函数自动加载第五次提案,7 月 30 日开投,一天后作者自己撤了。Tim Düsterhus 指出程序上不合规——投票前七天没有发 intent to vote 的邮件。撤的时候票数是 2:11。两周内这是第二场被作者自己叫停的投票,都栽在同一条规则上。

七、我把自己的仓库扫了一遍

我手上这个博客仓库有 626 个 PHP 文件。我把 8.6 弃用清单里最容易扫的 11 个函数挨个 grep 了一遍——mb_ereg、mb_split、mb_eregi、spl_object_hash、spl_classes、strcoll、metaphone、is_double、is_integer、is_long、doubleval。

自有代码:0 处命中。

vendor 里:spl_object_hash 15 个文件,strcoll 1 个,is_integer 1 个。

这个结果改了我的升级计划。我本来以为要花半天改自己的代码,现在看,我这轮升级真正要等的不是我自己的编辑器,是那 17 个文件的维护者。 composer.json 里那行 PHP 版本约束放宽一档是十秒钟的事,之后能不能升,取决于 Laravel 和那些包什么时候出兼容 8.6 的版本。

给你一个可以直接抄的动作:

php -d error_reporting=E_ALL -d display_errors=1 vendor/bin/phpunit

数一数输出里 E_DEPRECATED 有多少行。那个行数就是你的升级工作量估算。

这里有个坑要单独提醒:很多 CI 会把 E_DEPRECATED 屏蔽掉。 那样测试是绿的、版本照发,而所有这些弃用会原封不动地带进 PHP 9.0,在那里变成硬错误。现在把它打开,是你能做的最便宜的一件事。

八、运维侧两个会真咬人的改动

一个跟报错有关,一个跟进程退出有关。

gd、gmp、intl、pcntl、soap、sockets、zip 这几个扩展在 8.6 里把 TypeError 和 ValueError 收紧了。以前静默接受的参数,现在会抛。

另一个更要紧:硬性内存耗尽时,8.6 用 abort() 替代 exit(1),退出码从 1 变成 134,并且会产生 core dump。

如果你的监控告警里有一条是按「进程退出码 = 1」识别 PHP OOM 的,8.6 上线之后那条规则会集体失声。core dump 对排查是好事,但前提是磁盘扛得住——批量产生 core 文件把盘写满,是另一种事故。

session 三个默认值和 mbstring 正则的弃用,我在上一篇 8.6 的文章里写过,这里不重复。

九、下注

我赌 static 推断在 8.7 之前不会回来。

理由不在技术难度,在流程:它卡在「要先弃用内部函数通过上一帧做隐式实例调用的行为」上,而弃用投票一年只有一次窗口。等弃用落地、再等移除、再等推断重新实现,最快也是 8.8 的事。

8.7 的提案里如果它回来了,我回来把这篇改掉。

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

延伸阅读:

GPT-6.1 Sol 发布:五分之一价格拿到 Astra 能力,同一周更强的 Astra 却被取消了 AI与大模型
GPT-6.1 Sol 发布:五分之一价格拿到 Astra 能力,同一周更强的 Astra 却被取消了

GPT-6.1 Sol 用约五分之一的价格拿到接近 Astra 的成绩,但同一周更强的 GPT-6.1 Astra 被取消发布,理由是欺骗性汇报与越权执行。本文拆解三档版本关系、缓存降价与推理档位收紧的真实影响、取消事件的时间线,以及生产环境 Agent 防范假完成报告与越权执行的四道工程防线。

AI 后端 安全

Valencio

/

2026-10-08

GPT-6 Sol 和 Luna 发布:价格砍到 Astra 的 1%,但按价签选模型会踩坑 行业快讯
GPT-6 Sol 和 Luna 发布:价格砍到 Astra 的 1%,但按价签选模型会踩坑

9 月 22 日 OpenAI 补上 GPT-6 的 Sol 和 Luna 两档,价格降到 Astra 的 1/5 和 1/100。但重点不是便宜:三档能力差距按工作类型分叉——编码任务上 Luna 只落后 Sol 2.2 分,智能体任务上却掉 12.5 分。本文附三档规格对比、计费隐藏条款、GPT-6 Sol 与前代的代际对比,以及一段可直接抄走的 PHP 模型路由实现。

AI 后端

Valencio

/

2026-09-23