第一級callableを生成する記法

第一級callableを生成する記法 (First class callable syntax) とは、PHP 8.1.0 で追加された、 callable から 無名関数 を生成する記法です。 この記法は、 文字列や配列を使って callable を生成するやり方を置き換えるものです。 この記法の利点は、callable の静的解析を行いやすくなることに加え、 そのスコープが、callable を生成した時点のスコープになることです。

CallableExpr(...) という記法で、 callable から 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'](...);
?>

注意:

... は文法の一部であり、省略形ではありません。

この記法の動作は、 Closure::fromCallable() の仕様に従います。 つまり、文字列や配列から callable を作るやり方とは異なり、 CallableExpr(...) のスコープは、 それを生成した時点でのスコープになります:

例2 古い callable の生成方法と、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() {
// 下記は、callable を生成した時点のスコープになります。
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() は、呼び出しとは見なされないからです。

注意:

この記法は、nullsafe 演算子 と組み合わせて使うことはできません。 以下のコードは、いずれもコンパイルエラーになります:

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

add a note

User Contributed Notes 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