PHP Conference Japan 2024

一等可调用语法

一等可调用语法从 PHP 8.1.0 开始引入,作为从 匿名函数 创建 可调用 的一种方式。它取代了使用字符串和数组的现有可调用语法。这种语法的优点是它可以被静态分析访问,并使用获取可调用对象时的作用域。

CallableExpr(...) 语法用于从可调用对象创建一个 Closure 对象。CallableExpr 接受任何可以在 PHP 语法中直接调用的表达式。

示例 #1 简单的一等可调用语法

<?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(...);

// 使用字符串、数组的传统可调用方式
$f7 = 'strlen'(...);
$f8 = [$obj, 'method'](...);
$f9 = [Foo::class, 'staticmethod'](...);
?>

注意:

... 是语法的一部分,而不是省略。

CallableExpr(...)Closure::fromCallable() 具有相同的语义。也就是说,与使用字符串和数组的可调用对象不同,CallableExpr(...) 尊重创建它的位置的作用域。

示例 #2 CallableExpr(...) 和传统可调用对象的作用域比较

<?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() {
// 使用获取可调用对象时的作用域。
return $this->privateMethod(...); // 等同于 Closure::fromCallable([$this, 'privateMethod']);
}

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

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

注意:

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

注意:

一等可调用语法不能与 空安全运算符 组合使用。以下两者都会导致编译时错误。

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

添加备注

用户贡献的备注 1 条备注

15
bienvenunet at yahoo dot com
1 年前
这种语法有一个主要的陷阱,可能直到你使用这种语法并发现你在某些随机的库代码中得到“无法重新绑定从方法创建的闭包的作用域”异常时才会显现。

正如文档中所指出的,一等可调用对象使用获取可调用对象时的作用域。只要你的代码中没有任何内容会尝试使用 \Closure::bindTo 方法绑定可调用对象,这都没问题。

我通过将传递给 Laravel 的 Macroable 功能的可调用对象从数组样式更改为一等可调用对象样式,以艰难的方式发现了这一点。Macroable 功能对可调用对象调用 \Closure::bindTo。

据我所知,唯一的解决方法是使用更难看的数组语法。
To Top