编程

探索 PHP 的 First-class callable 语法

928 2023-05-02 10:29:00

查看 Laravel 框架最新的更新时,我发现了一些以前没看到过的语法。

比如 laravel/framework 中的 bin/facades.php 文件

 $resolvedMethods = $proxies->map(fn ($fqcn) => new ReflectionClass($fqcn))
        ->flatMap(fn ($class) => [$class, ...resolveDocMixins($class)])
        ->flatMap(resolveMethods(...))
        ->reject(isMagic(...))
        ->reject(isInternal(...))
        ->reject(isDeprecated(...))
        ->reject(fulfillsBuiltinInterface(...))
        ->reject(fn ($method) => conflictsWithFacade($facade, $method))
        ->unique(resolveName(...))
        ->map(normaliseDetails(...));

我未见过的语法是函数调用里的(…):

->flatMap(resolveMethods(...))

你可能在各自其他语境下见过 操作符(也叫扩展操作符或解包运算符)

你可以用来对数组解包

$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon'];

... 或者获取函数参数:

function myFunction(...$arguments) {
	var_dump($arguments); // shows an array ['a', 'b', 'c']
}
	
myFunction('a', 'b', 'c');

还有一个巧妙的小技巧,即你可以将一个函数的全部参数传入到另外一个函数中。

function myFunction(...$arguments) {
	// $arguments now holds an array of all passed arguments
	
	// all elements in the array will be passed as 
	//separate arguments to `anotherFunction`.
	anotherFunction(...$arguments);
}

First-class callable 语法

而这里的情况...

->flatMap(resolveMethods(...))

此处的 … 运算符则完全不同。此处被称为 ”first class callable",是PHP8.1中引入的新语法。它将要使用的函数包装在闭包中。

因此这段代码 ...

$myFunction = strtoupper(...);

... 和下面这段代码是等价的:

$myFunction = function(...$arguments) {
	return strtoupper(...$argument);
}

你传入其中的所有参数,都会传入到闭包中包裹的函数中。

我们来试试使用它吧

$myFunction('a') // returns 'A';

我们先用简单的示例试试。假设你有个集合,需要将其中的元素转换成大写。

collect(['a', 'b', 'c'])
   ->map(function($letter) {
      return strtoupper($letter);
   });

使用新语法,你可以这样重写代码:

collect(['a', 'b', 'c'])
   ->map(strtoupper(...));

酷吧?

当然,你也可以使用非全局函数,比如类方法。

class MyClass()
{
    public function execute(): Collection
    {
        return collect(['a', 'b', 'c'])
            ->map($this->doubleString(...));
    }
    
    public function doubleString(string $string): string
    {
        return $string . $string;
    }
}

 // returns a collection with 'aa', 'bb, and 'cc'.
(new MyClass)->execute();

很简洁!