Бинтаксис создания пСрвоклассных callable-Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ

Бинтаксис прСобразования callable-Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ ΠΏΠ΅Ρ€Π²ΠΎΠ³ΠΎ класса, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Ρ‚ΡŒ ΠΊΠ°ΠΊ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚, Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ ΠΈΠ· Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ ΠΈΠ»ΠΈ ΠΏΡ€ΠΈΡΠ²Π°ΠΈΠ²Π°Ρ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΌ, прСдставили Π² PHP 8.1.0 ΠΊΠ°ΠΊ способ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ создаёт Π°Π½ΠΎΠ½ΠΈΠΌΠ½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ ΠΈΠ· callable-Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ. Новый синтаксис вытСсняСт ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ callable-синтаксис со строками ΠΈ массивами. ΠŸΡ€Π΅ΠΈΠΌΡƒΡ‰Π΅ΡΡ‚Π²ΠΎ Π½ΠΎΠ²ΠΎΠ³ΠΎ синтаксиса состоит Π² доступности для статичСского Π°Π½Π°Π»ΠΈΠ·Π° ΠΈ наслСдовании Π½ΠΎΠ²Ρ‹ΠΌ синтаксисом области видимости ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… Π² Ρ‚ΠΎΡ‡ΠΊΠ΅ получСния callable-выраТСния.

Бинтаксис CallableExpr(...) создаёт ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Closure ΠΈΠ· выраТСния, доступного для Π²Ρ‹Π·ΠΎΠ²Π°, Π³Π΄Π΅ CallableExpr β€” элСмСнт синтаксиса, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅, доступноС для прямого Π²Ρ‹Π·ΠΎΠ²Π° Π² Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Ρ… PHP-Π³Ρ€Π°ΠΌΠΌΠ°Ρ‚ΠΈΠΊΠΈ:

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #1 ΠŸΡ€ΠΈΠΌΠ΅Ρ€ создания пСрвоклассных Π²Ρ‹Π·Ρ‹Π²Π°Π΅ΠΌΡ‹Ρ… Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ синтаксисом с ΠΌΠ½ΠΎΠ³ΠΎΡ‚ΠΎΡ‡ΠΈΠ΅ΠΌ

<?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(...); // Π’Ρ‹Π·ΠΎΠ² ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° ΠΊΠ°ΠΊ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
$f3 = $obj->method(...);
$f4 = $obj->$methodStr(...);
$f5 = Foo::staticmethod(...);
$f6 = $classStr::$staticmethodStr(...);

// Π’Ρ€Π°Π΄ΠΈΡ†ΠΈΠΎΠ½Π½Ρ‹ΠΉ синтаксис callable-Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ со строками ΠΈ массивами
$f7 = 'strlen'(...);
$f8 = [$obj, 'method'](...);
$f9 = [Foo::class, 'staticmethod'](...);

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅:

ΠžΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ ... β€” Ρ‡Π°ΡΡ‚ΡŒ синтаксиса, Π° Π½Π΅ пропуск.

Π’Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ CallableExpr(...) ΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄ Closure::fromCallable() сСмантичСски ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ‡Π½Ρ‹. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ Π² ΠΎΡ‚Π»ΠΈΡ‡ΠΈΠ΅ ΠΎΡ‚ callable-синтаксиса со строками ΠΈ массивами, синтаксис CallableExpr(...) ΡƒΡ‡ΠΈΡ‚Ρ‹Π²Π°Π΅Ρ‚ ΠΎΠ±Π»Π°ΡΡ‚ΡŒ видимости контСкста, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ создаёт Π·Π°ΠΌΡ‹ΠΊΠ°Π½ΠΈΠ΅:

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ #2 Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ области видимости синтаксиса CallableExpr(...) ΠΈ Ρ‚Ρ€Π°Π΄ΠΈΡ†ΠΈΠΎΠ½Π½ΠΎΠ³ΠΎ callable-синтаксиса

<?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
// ΠŸΡ€ΠΈΡ‡ΠΈΠ½Π° Ρ„Π°Ρ‚Π°Π»ΡŒΠ½ΠΎΠΉ ошибки состоит Π² Π²Ρ‹Π·ΠΎΠ²Π΅ замыкания Π²Π½Π΅ класса Foo,
// ΠΏΡ€ΠΈ Ρ‚ΠΎΠΌ Ρ‡Ρ‚ΠΎ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° провСряСтся Π² контСкстС Π²Ρ‹Π·ΠΎΠ²Π°, Π° Π½Π΅ опрСдСлСния
<?php
class Foo1
{
public function
getPrivateMethod()
{
// Callable-Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ унаслСдуСт ΠΎΠ±Π»Π°ΡΡ‚ΡŒ видимости ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ…,
// Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚
return $this->privateMethod(...); // Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π²ΠΎΠ·Π²Ρ€Π°Ρ‚Π° ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ‡Π½ΠΎ
// Π·Π½Π°Ρ‡Π΅Π½ΠΈΡŽ Π²Ρ‹Π·ΠΎΠ²Π° Closure::fromCallable([$this, 'privateMethod']);
}

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

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

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅:

Бинтаксисом Π½Π°ΠΏΠΎΠ΄ΠΎΠ±ΠΈΠ΅ new Foo(...) нСльзя ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚, ΠΏΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ синтаксис new Foo() Π½Π΅ относится ΠΊ Π²Ρ‹Π·ΠΎΠ²Π°ΠΌ.

Π—Π°ΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅:

Бинтаксис, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ создаёт ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ ΠΏΠ΅Ρ€Π²ΠΎΠ³ΠΎ класса ΠΈΠ· callable-Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ, нСльзя ΠΊΠΎΠΌΠ±ΠΈΠ½ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ с null-бСзопасным ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠΌ. КаТдая ΠΈΠ· ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΡ… строк Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΎΡˆΠΈΠ±ΠΊΡƒ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ компиляции:

<?php
$obj
?->method(...);
$obj?->prop->method(...);
οΌ‹Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ 2 notes

up
22
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.
up
4
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.