PHP 8.1: First-class 可调用(callable)语法
PHP 8.1 及以上版本支持在当前作用域中创建 callable 的新语法。
相比用 Closure::fromCallable,借助该语法使用函数或方法的调用方式来创建 callable 使之更容易。
Closure::fromCallable 从PHP callable(函数名,方法或匿名函数)中返回一个 callable (Closure 对象) 。该语法旨在减少Closure::fromCallable的模板代码。
比如,下例返回调用strtoupper的callable:
$callable = Closure::fromCallable('strtoupper');
echo $callable('foo'); // FOO
使用新语法,同样的callable可以这样创建:
-$callable = Closure::fromCallable('strtoupper');
+$callable = strtoupper(...);
echo $callable('foo'); // FOO
语法
一个后面跟着(…)的有效的可调用(callable)类型,称之为 Fist-class Callable。
函数名
$callable = strlen(...);
类方法
$callable = $item->doSomething(...);
静态方法
$callable = $item::doSomething(...);
匿名函数
$function = function() {};
$callable = $function(...);
请注意,… 不是被用作特定参数的占位符。虽然该语法允许部分函数使用,此处却不适用。试图传参到这个First-class callable中会产生语法错误:
$callable = str_contains($text, ...);
Parse error: syntax error, unexpected token ")" in ... on line ...
PHP 也使用 … (内部叫做T_ELLIPSIS) 作为可变函数声明及在调用时作为扩展运算符。这对可变函数声明及扩展运算符的行为没有影响。
限制
不允许对象实例化
这个First-class callable语法不支持使用新构造实例化新对象。此行为类似于Closure::fromCallable。
$callable = new SplFixedArray(...);
Fatal error: Cannot create Closure for new expression in ... on line ...
不允许nullsafe方法
该语法不允许nullsafe方法,因为它不能保证可调用。
$test?->doSomething(...);
Fatal error: Cannot combine nullsafe operator with Closure creation in ... on line ...
注解参数
使用该语法不能声明注解
#[Attribute(...)]
class Test {}
Fatal error: Cannot create Closure as attribute argument in ... on line ...
Callable 作用域
一个 First-class callable 被创建时,它也同时继承了创建该callable的调用栈的作用域。
function shout(): void {
$value = 'Banana';
echo $value;
}
$value = 'Apple';
$callable = shout(...);
$callable(); // Banana
因为First-class callable 有作用域,因此可以返回一个 callable 来调用私有方法,只要它是从对象作用域内返回的。
class Clock {
public function getClockCallable(): callable {
return $this->getTime(...);
}
private function getTime(): int {
return time();
}
}
$clock = new Clock();
$clock_callback = $clock->getClockCallable();
echo $clock_callback();
注意,Clock::getClockCallable 返回了一个 callable,该 callable 调用了私有方法 getTime。现有使用数组创建callable的语法不允许调用私有方法:
class Clock {
public function getClockCallable(): callable {
- return $this->getTime(...);
+ return [$this, 'getTime'];
}
private function getTime(): int {
return time();
}
}
$clock = new Clock();
$clock_callback = $clock->getClockCallable();
echo $clock_callback();
Fatal error: Uncaught Error: Call to private method Clock::getTime() from global scope in ...:...
向后兼容性影响
First-class callable 语法是 PHP 8.1 中新引入的语法,无法打补丁到旧版PHP中。尝试使用会导致解析错误:
Parse error: syntax error, unexpected token ")" in ... on line ...
需要兼容旧版的应用可以继续使用 Closure::fromCallable 去创建有范围的callable。