Callback-Funktionen als Objekte erster Klasse
Callback-Funktionen als Objekte erster Klasse wurde mit PHP 8.1.0
eingeführt, um
anonyme Funktionen aus
Callback-Funktionen zu
erstellen. Sie ersetzt die bestehende Callback-Syntax, die Strings und
Arrays verwendet. Der Vorteil dieser Syntax ist, dass sie für die
statische Analyse zugänglich ist und den Gültigkeitsbereich an der Stelle
verwendet, an der die Callback-Funktion aufgerufen wird.
Die CallableExpr(...)-Syntax wird verwendet, um ein
Closure-Objekt aus einer Callback-Funktion zu
erzeugen. CallableExpr akzeptiert jeden Ausdruck, der in der
PHP-Grammatik direkt aufgerufen werden kann:
Beispiel #1 Einfaches Beispiel für eine Callback-Funktion als Objekt erster Klasse
<?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(...); // Callback-Objekt
$f3 = $obj->method(...);
$f4 = $obj->$methodStr(...);
$f5 = Foo::staticmethod(...);
$f6 = $classStr::$staticmethodStr(...);
// traditionelle Callback-Funktion mit String, Array
$f7 = 'strlen'(...);
$f8 = [$obj, 'method'](...);
$f9 = [Foo::class, 'staticmethod'](...);
?>
Hinweis:
Das ... ist keine Auslassung, sondern Teil der Syntax.
CallableExpr(...) hat die gleiche Semantik wie
Closure::fromCallable(). Das heißt, im Gegensatz zu
Callback-Funktionen, die Strings und Arrays verwenden, wird bei
CallableExpr(...) der Geltungsbereich an dem Punkt
berücksichtigt, an dem er erstellt wird:
Beispiel #2 Vergleich des Anwendungsbereichs von CallableExpr(...) und traditionellen Callback-Funktionen
<?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
// Dies liegt daran, dass der Aufruf außerhalb von Foo erfolgt und die Sichtbarkeit ab diesem Punkt geprüft wird.
class Foo1 {
public function getPrivateMethod() {
// Verwendet den Bereich, in dem die Callback-Funktion aufgerufen wird.
return $this->privateMethod(...); // identisch mit Closure::fromCallable([$this, 'privateMethod']);
}
private function privateMethod() {
echo __METHOD__, "\n";
}
}
$foo1 = new Foo1;
$privateMethod = $foo1->getPrivateMethod();
$privateMethod(); // Foo1::privateMethod
?>
Hinweis:
Die Erzeugung von Objekten mit dieser Syntax (z. B.
new Foo(...)) wird nicht unterstützt, da die
new Foo()-Syntax nicht als Aufruf betrachtet wird.
Hinweis:
Callback-Funktionen als Objekte erster Klasse können nicht mit dem
Nullsafe-Operator
kombiniert werden. Beides führt zu einem Fehler bei der Kompilierung:
bienvenunet at yahoo dot com ¶3 years 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.
dan dot feder at civicactions dot com ¶1 year 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.