Sintaxis callable de primera clase

La sintaxis de callable de primera clase es introducida a partir de PHP 8.1.0, como una manera de crear funciones anónimas a partir de callable. Reemplaza la sintaxis de callables existente utilizando cadenas y arrays. La ventaja de esta sintaxis es que es accesible al análisis estático y utiliza el ámbito del punto donde el callable es adquirido.

La sintaxis CallableExpr(...) es utilizada para crear un objeto Closure a partir del callable. CallableExpr acepta cualquier expresión que pueda ser directamente llamada en la gramática de PHP:

Ejemplo #1 Sintaxis callable de primera clase básica

<?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(...); // invokable object
$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'](...);
?>

Nota:

Los ... forman parte de la sintaxis y no son una omisión.

CallableExpr(...) tiene las mismas semánticas que Closure::fromCallable(). Es decir, a diferencia de los callables utilizando cadenas y arrays, CallableExpr(...) respeta el ámbito del punto donde es creado:

Ejemplo #2 Comparación de ámbito de CallableExpr(...) y de callables tradicionales

<?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
?>

Nota:

La creación de objetos con esta sintaxis (e.g new Foo(...)) no es soportada, porque la sintaxis new Foo() no es considerada como una llamada.

Nota:

La sintaxis de callable de primera clase no puede ser combinada con el operador nullsafe. Los dos casos siguientes resultan en un error de compilación:

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

add a note

User Contributed Notes 2 notes

up
20
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.
up
1
dan dot feder at civicactions dot com
1 month ago
Also note that closures are not serializable. So if you are storing a reference to a callback in a variable that will be serialized for caching or any other purpose, do not switch to this syntax or your serialization will break.
To Top