PHP 8.6 部分函数应用:用 ? 预填参数,少写一层 use 和临时闭包
PHP 8.6 的 GA 定在 2026-11-19,alpha/beta/RC 铺满全年,节点见官方时间表:。项目主页:。
这一版里跟日常写业务代码关系最近的,是已经投票通过的部分函数应用(partial function application)。
? 占位符做什么
在函数调用里写 ?,表示这个位置的参数先空着,调用表达式本身变成一个闭包:
$replace = str_replace('a', 'b', ?);
$replace('banana'); // 'bbnbnb'
str_replace 的前两个实参在写这一行的时候就固定了,剩下第三个参数留给调用方。产物是一个 Closure,可以直接当 callback 用:
$keys = ['user_id', 'user_name', 'user_email'];
$dashed = array_map(str_replace('_', '-', ?), $keys);
以前这种位置要么现写一个箭头函数,要么写 use 兜住外部变量。
和闭包、一等可调用对象的区别
三种写法产出的都是 Closure,差别在参数从哪来:
// 一等可调用对象(PHP 8.1):整体变成闭包,参数一个都不填
$f = str_replace(...);
// 自己写闭包:参数、类型、默认值都由你声明
$f = fn(string $s) => str_replace('a', 'b', $s);
// 部分函数应用:函数本体 + 已经写死的实参,剩下的位置由 ? 让出
$f = str_replace('a', 'b', ?);
use 捕获的是外部作用域里的变量,闭包体里还要再写一遍调用;PFA 是把这行代码里已经写死的实参直接固化进调用位置,外面不需要多一个变量名,也不需要多一层 use (...)。
省掉的东西
典型的一段:前缀剥离。
// before
$prefix = 'user_';
$strip = function (string $key) use ($prefix): string {
return str_replace($prefix, '', $key);
};
// 8.6
$strip = str_replace('user_', '', ?);
数组映射、字符串规整这类一行回调尤其明显——闭包体只剩一个函数调用的时候,fn($x) => f($x) 基本可以被 f(?) 替掉,读起来只剩被调用的那个函数名。
可读性上的取舍
str_replace('a', 'b', ?) 这种参数少、位置一目了然的情况,比箭头函数更直接。
参数一多就不一定了。占位符是按位置生效的,人眼得先数清楚 ? 落在第几个参数上,才知道生成的闭包要几个实参、顺序是什么。参数签名长、或者 ? 夹在中间的时候,一个正常的 fn 反而更快读懂。
另外需要类型声明、默认值、多步逻辑的场景,PFA 都表达不了,老老实实写闭包。它是一个替掉最薄那层包装的工具,不是替代闭包。
常见误用
以为 ? 会自动补全参数。 它只是让出这个位置,不是「可选参数」。生成的闭包照样要求调用方把空位补齐。
想跳过中间参数只固定后面的。 位置是硬约束。要固定第三个参数,前两个位置就得用 ? 占着,调用方按顺序传进来——这种时候不如直接写闭包。
把位置写错。 str_replace(?, 'b', $s) 生成的是「只差第一个参数」的闭包,跟 str_replace('a', 'b', ?) 完全不是一回事。语法上不报错,跑起来才发现参数对不上。
在循环里反复构造。 这个和下面那条优化直接相关。
同批的闭包优化:只落了缓存这一半
这一版的闭包相关 RFC 原本是两块:
- 对从不接触
$this的闭包自动推断为static; - 把完全无状态的闭包缓存起来,PHP 不再每次执行到那一行就分配一个新的闭包对象。
8.6 只落了第二块。自动推断 static 那一半被放弃了——实现没法可靠地识别出全部边界情况,推断错方向会直接把 $this 的语义改掉,代价比收益大。所以依赖「闭包自动变 static」的写法在这一版里依然不成立,该手写 static fn 的还得手写。
json_decode() 现在会报出错位置
解析失败时会指出失败发生在字符串中的哪个位置。调试拼接出来的一长串 JSON 时,不用再靠肉眼从某个偏移量开始数。
8.6 里一起落地的其他东西
Io\Poll轮询 APITime\Duration- readonly 属性默认值
clamp()SortDirection枚举- 更安全的 session 默认值
- 类常量上的
#[\Override]
参考
- 发布时间表:
- PHP 官网: