编程

PHP 8.1: First-class 可调用(callable)语法

697 2023-04-26 03:49:00

PHP 8.1 及以上版本支持在当前作用域中创建 callable 的新语法。

相比用 Closure::fromCallable,借助该语法使用函数或方法的调用方式来创建 callable 使之更容易。

Closure::fromCallable 从PHP callable(函数名,方法或匿名函数)中返回一个 callable (Closure 对象) 。该语法旨在减少Closure::fromCallable的模板代码。 

比如,下例返回调用strtoupper的callable:

$callable = Closure::fromCallable('strtoupper');
echo $callable('foo'); // FOO

使用新语法,同样的callable可以这样创建:

-$callable = Closure::fromCallable('strtoupper');
+$callable = strtoupper(...);
echo $callable('foo'); // FOO

语法

一个后面跟着(…)的有效的可调用(callable)类型,称之为 Fist-class Callable。

函数名

$callable = strlen(...);

类方法

$callable = $item->doSomething(...);

静态方法

$callable = $item::doSomething(...);

匿名函数

$function = function() {};
$callable = $function(...);

请注意,… 不是被用作特定参数的占位符。虽然该语法允许部分函数使用,此处却不适用。试图传参到这个First-class callable中会产生语法错误:

$callable = str_contains($text, ...);
Parse error: syntax error, unexpected token ")" in ... on line ...

PHP 也使用 …  (内部叫做T_ELLIPSIS) 作为可变函数声明及在调用时作为扩展运算符。这对可变函数声明及扩展运算符的行为没有影响。

限制

不允许对象实例化

这个First-class callable语法不支持使用新构造实例化新对象。此行为类似于Closure::fromCallable。

$callable = new SplFixedArray(...);
Fatal error: Cannot create Closure for new expression in ... on line ...

不允许nullsafe方法

该语法不允许nullsafe方法,因为它不能保证可调用。

$test?->doSomething(...);
Fatal error: Cannot combine nullsafe operator with Closure creation in ... on line ...

注解参数

使用该语法不能声明注解

#[Attribute(...)]
class Test {}
Fatal error: Cannot create Closure as attribute argument in ... on line ...

Callable 作用域

一个 First-class callable 被创建时,它也同时继承了创建该callable的调用栈的作用域。

function shout(): void {
    $value = 'Banana';
    echo $value;
}

$value = 'Apple';
$callable = shout(...);

$callable(); // Banana

因为First-class callable 有作用域,因此可以返回一个 callable 来调用私有方法,只要它是从对象作用域内返回的。

class Clock {
    public function getClockCallable(): callable {
        return $this->getTime(...);
    }

    private function getTime(): int {
        return time();
    }
}

$clock = new Clock();
$clock_callback = $clock->getClockCallable();
echo $clock_callback();

注意,Clock::getClockCallable 返回了一个 callable,该 callable 调用了私有方法 getTime。现有使用数组创建callable的语法不允许调用私有方法:

class Clock {
    public function getClockCallable(): callable {
-       return $this->getTime(...);
+       return [$this, 'getTime'];
    }

    private function getTime(): int {
        return time();
    }
}

$clock = new Clock();
$clock_callback = $clock->getClockCallable();
echo $clock_callback();
Fatal error: Uncaught Error: Call to private method Clock::getTime() from global scope in ...:...

向后兼容性影响

First-class callable 语法是 PHP 8.1 中新引入的语法,无法打补丁到旧版PHP中。尝试使用会导致解析错误:

Parse error: syntax error, unexpected token ")" in ... on line ...

需要兼容旧版的应用可以继续使用 Closure::fromCallable 去创建有范围的callable。