PHPCon Poland 2024

Sintassi delle callable di prima classe

La sintassi delle callable di prima classe è stata introdotta a partire da PHP 8.1.0, come un modo per creare funzioni anonime da callable. Sostituisce la sintassi delle callable esistente utilizzando stringhe ed array. Il vantaggio di questa sintassi è che è accessibile dall'analisi statica e utilizza lo scope nel punto in cui viene acquisita la callable.

La sintassi CallableExpr(...) viene utilizzata per creare un oggetto Closure dalla callable. CallableExpr accetta qualsiasi espressione che puó essere richiamata direttamente nella grammatica PHP:

Example #1 Sintassi della callable di prima classe semplice

<?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(...); // oggetto invocabile
$f3 = $obj->method(...);
$f4 = $obj->$methodStr(...);
$f5 = Foo::staticmethod(...);
$f6 = $classStr::$staticmethodStr(...);

// callable tradizionale usando string, array
$f7 = 'strlen'(...);
$f8 = [$obj, 'method'](...);
$f9 = [Foo::class, 'staticmethod'](...);
?>

Nota:

I ... fanno parte della sintassi e non sono un'omissione.

CallableExpr(...) ha la stessa semantica di Closure::fromCallable(). Ovvero, a differenza di callable che utilizzano stringhe e array, CallableExpr(...) rispetta lo scope nel punto in cui viene creato:

Example #2 Confronto dello scope tra CallableExpr(...) e callable tradizionale

<?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
// Questo perché la chiamata viene eseguita all'esterno di Foo e la visibilità verrà controllata da questo punto.

class Foo1 {
public function
getPrivateMethod() {
// Utilizza lo scope in cui viene acquisita la callable.
return $this->privateMethod(...); // uguale a Closure::fromCallable([$this, 'privateMethod']);
}

private function
privateMethod() {
echo
__METHOD__, "\n";
}
}

$foo1 = new Foo1;
$privateMethod = $foo1->getPrivateMethod();
$privateMethod(); // Foo1::privateMethod
?>

Nota:

La creazione di oggetti con questa sintassi (ad esempio new Foo(...)) non è supportata, perché la sintassi new Foo() non è considerata una chiamata.

Nota:

La sintassi della callable di prima classe non può essere combinata con l'operatore nullsafe. Entrambi i seguenti porteranno ad un errore in fase di compilazione:

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

add a note

User Contributed Notes 1 note

up
11
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