编程 PHP 8.6 部分函数应用:`?` 与 `...` 生成的闭包签名、可选性变化和报错

2026-10-20 21:02:49

PHP 8.6 部分函数应用:? 与 ... 生成的闭包签名、可选性变化和报错

信源:

核心语义

RFC 原文:

Partial Application consists of a function call in which one or more arguments is replaced with ? (question mark) or ... (an ellipsis). If that is found, then rather than calling the function the engine will create a Closure which stores the function and the arguments that were provided and return that. The Closure signature will be created to match the signature of the un-specified parameters. That means the closure can be inspected via reflection and will display the full type information for both parameters and the return value derived from the underlying function.

也就是说,函数调用里有一个或多个参数被替换成 ? 或 ... 时,引擎不会执行函数,而是返回一个 Closure。这个闭包保存原函数和已经提供的参数,签名对应尚未指定的参数,因此可以用反射查看参数和返回值的完整类型信息。

等价示例

RFC 给出的等价关系:

function foo(int $a, int $b, int $c, int $d): int { return $a+$b+$c+$d; }
$f = foo(1, ?, 3, 4);   // == static fn(int $b): int => foo(1,$b,3,4);
$f = foo(1, ?, 3, ?);   // == static fn(int $b,int $d): int => foo(1,$b,3,$d);
$f = foo(1, ...);       // == static fn(int $b,int $c,int $d): int => foo(1,$b,$c,$d);
$f = foo(1, 2, ...);    // == static fn(int $c,int $d): int => foo(1,2,$c,$d);

PFA 的等价形式是 static fn(...)。区别在于占位符由引擎生成闭包,参数名、类型、默认值、引用传递/返回等从原函数继承;fn 需要自己写参数列表。

占位符

  • ? 表示该位置恰好一个参数。
  • ... 表示该位置零个或多个参数,只能出现一次,且后面只能跟命名参数。命名参数必须在所有占位符之后。

继承的内容

PFA 生成的闭包会继承:

  • 参数名
  • 类型
  • 可选性(后续 RFC 有变更,见下文)
  • 默认值
  • 引用传递/返回
  • #[NoDiscard] 和 #[SensitiveParameter] 两个属性

只复制这两个属性。#[Deprecated]、#[Override] 和自定义属性不复制。原因是 PFA 会生成真正的中间闭包,不像 FCC 只做引用;复制 #[Deprecated] 会产生两次警告。

手册示例

function add(int $a, int $b, int $c = 0): int { return $a+$b+$c; }
$addOne = add(1, ?);
var_dump($addOne(5)); // int(6)
$addToOne = add(1, ...);
var_dump($addToOne(2,3)); // int(6)
$stripSpaces = str_replace(' ', '', ?);
var_dump($stripSpaces('a b c')); // string(3) "abc"

后续 RFC:可选参数不再继承可选性

已通过的 optional_placeholder RFC 改变了可选参数的继承规则:

Parameters generated from ? placeholders shall consistently be non-optional parameters. Parameters that are pulled in by a ... placeholder shall continue to inherit their 'optionality'.

示例:

function example(mixed $a, string $b='default', string $c='also optional') {}
$c = example(?, ?);  // 等价 static fn(mixed $a, string $b) => example($a,$b);  // $b 变成必填
$c = example('foo', ...); // 仍保留 $b/$c 的默认值

这样改的原因是:? 在视觉上已经隐含“这里要传两个参数”;同时为将来支持 $this 的部分应用铺路。子类可以把必填改成可选,默认值不属于签名本身,无法安全继承。

命名参数

命名参数可以带值,占位符只能是位置占位,即 “Named placeholders are not supported”。

使用 ... 且没有剩余参数时,得到的闭包零参数,等于“延迟调用”(thunk):zero(...)(); 会直接打印。

报错原文

$c = stuff(?);  // Error: not enough arguments and or place holders for application of stuff
$c = stuff(?,?,?,?,?,?); // Error: too many arguments and or place holders for application of stuff
$c = stuff(?, ?, 3.5, $point, i: 5); // Error: Named parameter $i overwrites previous place holder
$c = stuff(i:1, ?, ?, ?, ?); // Fatal error: Named arguments must come after all place holders
$c = stuff(1, ?, 3.5, p: ?); // Parse error: syntax error, unexpected token "?"

与 FCC 及 func_get_args() 的差别

FCC 是 strlen(...) 这种直接引用原函数,attributes 全保留、不做中间闭包。PFA 生成真正的闭包,所以只保留两个有运行时语义的属性。

func_get_args() 存在一个 caveat。RFC 提到 “with one caveat noted below regarding func_get_args()”:通过 PFA 调用时,func_get_args() 看到的是实际传入的参数。

变参函数

用 ? 指向变参位会让变参变成必填。位置占位符落在变参区间时,参数按变参名加 0 基序号命名($nums0、$nums1),可按名字调用。

function foo(int $a=5, int $b=1, string ...$c) {}
$pfa = foo(?, ?, ?, ?); // == static fn(int $a,int $b,string $c0,string $c1) => foo($a,$b,$c0,$c1);

状态

已在 master(8.6 alpha3)落地。PHP 8.6.0 RC3 正在测试(2026-10-15 周 PHP Weekly)。

复制全文 生成海报 PHP PHP8.6 语法特性

推荐文章

程序员茄子在线接单