Statement on glibc/iconv Vulnerability

First class 可调用语法

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

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

示例 #1 简单的 first class 可调用语法

<?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(...) has the same semantics as Closure::fromCallable(). That is, unlike callable using strings and arrays, CallableExpr(...) respects the scope at the point where it is created:

示例 #2 Scope comparison of CallableExpr(...) and traditional 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
// This is because call is performed outside from Foo and visibility will be checked from this point.
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
?>

注意:

Object creation by this syntax (e.g new Foo(...)) is not supported, because new Foo() syntax is not considered a call.

注意:

The first-class callable syntax cannot be combined with the nullsafe operator. Both of the following result in a compile-time error:

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

add a note

User Contributed Notes 1 note

up
11
bienvenunet at yahoo dot com
10 months 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