Sintaxis de llamadas de retorno de primera clase

La sintaxis de llamadas de retorno de primera clase fue introducida desde PHP 8.1.0, como una forma de creación de funciones anónimas a partir de una llamada de retorno. Reemplaza la sintaxis de invocación existente que utiliza cadenas y arrays. La ventaja de esta sintaxis es que es accesible para el análisis estático y utiliza el alcance en el punto donde se adquiere la invocación.

La sintaxis CallableExpr(...) es usada para crear un objeto Closure a partir de una llamada de retorno. CallableExpr acepta cualquier expersión que pueda ser llamada en la sintaxis de PHP.

Ejemplo #1 Sintaxis de llamadas de retorno de primera clase

<?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(...); // Objeto invocable
$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:

El ... es parte de la sintaxis, y no una omisión.

CallableExpr(...) tiene la misma sintaxis que Closure::fromCallable(). Es decir, a diferencia de las llamadas que utilizan cadenas y arrays, CallableExpr(...) respeta el ámbito en el punto donde se crea:

Ejemplo #2 Comparación del ámbito de CallableExpr(...) y una llamada de retorno tradicional

<?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
// Esto es porque la llamada es realizada fuera de Foo y la visibilidad es comprobada en ese momento.

class Foo1 {
public function
getPrivateMethod() {
// Usa el ámbito donde la llamada de retorno es creada.
return $this->privateMethod(...); // Identico a 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 mediante esta sintaxis (por ejemplo, new Foo(...)) no está soportada, porque la sintaxis new Foo() no se considera una llamada.

Nota:

La sintaxis de llamada de retorno de primera clase no se puede combinar con el operador nullsafe. Ambos de los siguientes resultan en un error en tiempo de compilación:

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