PHP 8.4.2 Released!

First class callable 语法

从 PHP 8.1.0 开始引入 first class callable 语法,作为从 callable 创建匿名函数的一种方法。其取代了使用字符串和数组的现有 callable 语法。这种语法的优点是可以进行静态分析,并使用获得可调用对象的作用域。

CallableExpr(...) 语法用于从 callable 创建 ClosureCallableExpr 接受任何可以在 PHP 语法中直接调用的表达式:

示例 #1 简单的 first class callable 语法

<?php

class Foo {
public function
method() {}
public static function
staticmethod() {}
public function
__invoke() {}
}

$obj = new Foo();
$classStr = 'Foo';
$methodStr = 'method';
$staticmethodStr = 'staticmethod';


$f1 = strlen(...);
$f2 = $obj(...); // 可调用对象
$f3 = $obj->method(...);
$f4 = $obj->$methodStr(...);
$f5 = Foo::staticmethod(...);
$f6 = $classStr::$staticmethodStr(...);

// traditional callable using string, array
$f7 = 'strlen'(...);
$f8 = [$obj, 'method'](...);
$f9 = [Foo::class, 'staticmethod'](...);
?>

注意:

... 是语法的一部分,不是遗漏。

CallableExpr(...)Closure::fromCallable() 语义相同。也就是说,与使用字符串和数组的 callable 不同,CallableExpr(...) 尊重其创建时的作用域:

示例 #2 CallableExpr(...) 与传统 callable 的作用域比较

<?php

class Foo {
public function
getPrivateMethod() {
return [
$this, 'privateMethod'];
}

private function
privateMethod() {
echo
__METHOD__, "\n";
}
}

$foo = new Foo;
$privateMethod = $foo->getPrivateMethod();
$privateMethod();
// Fatal error: Call to private method Foo::privateMethod() from global scope
// 这是因为调用是在 Foo 外部执行的,并且将从此时检查可见性。

class Foo1 {
public function
getPrivateMethod() {
// Uses the scope where the callable is acquired.
return $this->privateMethod(...); // identical to Closure::fromCallable([$this, 'privateMethod']);
}

private function
privateMethod() {
echo
__METHOD__, "\n";
}
}

$foo1 = new Foo1;
$privateMethod = $foo1->getPrivateMethod();
$privateMethod(); // Foo1::privateMethod
?>

注意:

不支持通过此语法(例如 new Foo(...))创建对象,因为不会视 new Foo() 语法为调用。

注意:

first class callable 语法不能与 nullsafe 运算符结合使用。以下两种情况都会导致编译时错误:

<?php
$obj
?->method(...);
$obj?->prop->method(...);
?>

添加备注

用户贡献的备注 1 note

up
15
bienvenunet at yahoo dot com
1 year ago
There's a major gotcha with this syntax that may not be apparent until you use this syntax and find you're getting "Cannot rebind scope of closure created from method" exceptions in some random library code.

As the documentation indicates, the first-class callable uses the scope at the point where the callable is acquired. This is fine as long as nothing in your code will attempt to bind the callable with the \Closure::bindTo method.

I found this the hard way by changing callables going to Laravel's Macroable functionality from the array style to the first-class callable style. The Macroable functionality \Closure::bindTo calls on the callable.

AFAIK, the only workaround is to use the uglier array syntax.
To Top