Sintaxe de Callable de Primeira Classe

A sintaxe de callable de primeira classe é introduzida a partir do PHP 8.1.0, como uma maneira de criar funções anônimas a partir de callable. Ela substitui a sintaxe de callable existente usando strings e arrays. A vantagem dessa sintaxe é que ela é acessível à análise estática, e usa o escopo no ponto onde o callable é adquirido.

A sintaxe CallableExpr(...) é usada para criar um objeto Closure a partir de callable. CallableExpr aceita qualquer expressão que possa ser diretamente chamada na gramática do PHP:

Exemplo #1 Sintaxe de callable de primeira classe simples

<?php

class Foo {
public function
metodo() {}
public static function
metodoestatico() {}
public function
__invoke() {}
}

$obj = new Foo();
$strClasse = 'Foo';
$strMetodo = 'metodo';
$strMetodoestatico = 'metodoestatico';


$f1 = strlen(...);
$f2 = $obj(...); // objeto invocável
$f3 = $obj->metodo(...);
$f4 = $obj->$strMetodo(...);
$f5 = Foo::metodoestatico(...);
$f6 = $strClasse::$strMetodoestatico(...);

// Callable tradicional usando string, array
$f7 = 'strlen'(...);
$f8 = [$obj, 'metodo'](...);
$f9 = [Foo::class, 'metodoestatico'](...);
?>

Nota:

As ... são parte da sintaxe, e não uma omissão.

CallableExpr(...) tem a mesma semântica que Closure::fromCallable(). Isto é, That is, ao contrário de callable usando string e array, CallableExpr(...) respeita o escopo no ponto onde ela é criada:

Exemplo #2 Comparação de escopo de CallableExpr(...) e callable tradicional

<?php

class Foo {
public function
obterMetodoPrivado() {
return [
$this, 'metodoPrivado'];
}

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

$foo = new Foo;
$metodoPrivado = $foo->obterMetodoPrivado();
$metodoPrivado();
// Fatal error: Call to private method Foo::metodoPrivado() from global scope
// Isso acontece porque a chamada é realizada fora de Foo e a visibilidade será verificada a partir desse ponto.

class Foo1 {
public function
obterMetodoPrivado() {
// Usa o escopo onde o callable é adquirido.
return $this->metodoPrivado(...); // Idêntico a Closure::fromCallable([$this, 'metodoPrivado']);
}

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

$foo1 = new Foo1;
$metodoPrivado = $foo1->obterMetodoPrivado();
$metodoPrivado(); // Foo1::metodoPrivado
?>

Nota:

Criação de objetos por essa sintaxe (por exemplo new Foo(...)) não é suportada, porque a sintaxe new Foo() não é considerada uma chamada.

Nota:

A sintaxe de callable de primeira classe não pode ser combinada com o operador nullsafe. Ambos os resultados a seguir resultam em um erro de tempo de compilação:

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

add a note

User Contributed Notes 1 note

up
6
bienvenunet at yahoo dot com
8 months 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