First class callable syntax

Оновлено: 11.05.2023

Синтаксис виклику першого класу введено в PHP 8.1.0, як спосіб створення анонімних функцій з викликуваного класу. Він замінює існуючий синтаксис виклику з використанням рядків і масивів. Перевагою цього синтаксису є те, що він доступний для статичного аналізу і використовує область видимості в точці отримання виклику.

Синтаксис CallableExpr(...) використовується для створення об'єкта Closure з callable. 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(...);  // invokable object
$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'](...);
?>

Зауважте:

... є частиною синтаксису, а не пропуском.

CallableExpr(...) має ту ж саму семантику, що і Closure::fromCallable(). Тобто, на відміну від виклику з використанням рядків та масивів, CallableExpr(...) поважає область видимості в точці, де він створюється:

Приклад #2 Порівняння області видимості CallableExpr(...) та традиційного виклику

<?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
// This is because call is performed outside from Foo and visibility will be checked from this point.

class Foo1 {
    public function getPrivateMethod() {
        // Uses the scope where the callable is acquired.
        return $this->privateMethod(...); // identical to Closure::fromCallable([$this, 'privateMethod']);
    }

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

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

Зауважте:

Створення об'єктів за цим синтаксисом (наприклад, new Foo(...)) не підтримується, оскільки синтаксис new Foo() не вважається викликом.

Зауважте:

Синтаксис першокласного виклику не можна поєднувати з оператором nullsafe. Обидва варіанти призводять до помилки під час компіляції:

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