经过一番反复讨论,我们终于要在 PHP 8.5 中迎来管道运算符(pipe operator)了!本文将带你了解它的工作原理,以及我们为什么要使用它。
管道运算符的优势
想象有一个输入(比如一个字符串),你需要对它执行一系列操作。假设你要把它转换成一个 URL 友好的别名(slug)。
$input = ' Some kind of string. ';$output = strtolower(
str_replace(['.', '/', '…'], '',
str_replace(' ', '-',
trim($input)
)
)
);
你可能还能从自己的代码里想到不少类似例子,但这种写法存在几个问题:
首先,深层嵌套的函数调用很容易变得难以控制;
其次,代码格式会变得混乱——你得纠结是让每个新参数单独成行,还是每个函数调用单独成行;
第三,这段代码需要从内向外反向阅读:先执行
trim(),再执行str_replace(),以此类推。
一个可能的改进方案是引入临时变量:
$input = ' Some kind of string. ';$temp = trim($input);$temp = str_replace(' ', '-', $temp);$temp = str_replace(['.', '/', '…'], '', $temp);$output = strtolower($temp);
我得说,这种写法已经好很多了,而且解决了我刚才列出的所有问题;但缺点是引入了一个新的临时变量。这感觉有点……别扭。虽然算不上大问题,而且肯定比深层嵌套函数调用要好,但如果能让写法再便捷一点呢?这就是管道运算符的用途!以下是 PHP 8.5 中这段代码的写法:
$output = $input
|> trim(...)
|> fn (string $string) => str_replace(' ', '-', $string)
|> fn (string $string) => str_replace(['.', '/', '…'], '', $string)
|> strtolower(...);
看起来好多了!不过关于这个例子,有几点需要说明,我们来仔细看看。
“右侧”的规则
管道运算符的工作原理是:将左侧的内容“传递”到右侧。“左侧”很好理解——任何类型的输入都可以放在这里:字符串、数组、对象,或是上一个管道的输出。但“右侧”该怎么用?这里有几个要点需要注意。
第一种用法是传入一个可调用对象的引用,也就是所谓的“一等可调用(first-class callable)”:
$output = $input |> trim(...);
不过,这种用法有个限制:该可调用对象只能接受一个参数——这个参数就是管道传递过来的输入。换句话说,上面的例子在底层会被转换为:
$output = trim($input);
但如果要把输入传递给一个“接受多个参数”的函数呢?这时就需要使用可调用对象(callable)。这些可调用对象可以是短闭包或完整闭包、可调用类(invokable class),甚至可以是字符串形式或数组形式的可调用函数引用——只要它们只接受一个输入参数就行。就我个人而言,我更倾向于使用短闭包:
$output = $input |> fn (string $string) => str_replace(' ', '-', $string);
必须使用闭包这一点确实显得有些繁琐——甚至比我们用 $temp 变量的例子还要冗长!不过,我仍然更偏爱管道运算符而非 $temp 变量,因为“一段管道代码块”给人的感觉更像是“逻辑上属于一起的代码”,而用 $temp 变量时这种关联性没那么强。
此外,PHP 内核开发团队其实正在开发一个与管道运算符搭配使用的后续特性,名为“部分函数应用(partial function application)”。有了它,我们就能把上面的例子改写成这样:
// 目前还无法使用!$output = $input |> str_replace(' ', '-', ?);
注意到那个问号了吗?它是“缺失输入”的占位符,管道运算符传递过来的内容会填充到这个位置。如果能把这两个特性结合起来肯定很棒,但目前我们还得靠可调用对象来实现需求。
尽管存在这个小缺点,我仍然认为管道运算符是一个非常出色的新特性,并且已经在期待 PHP 8.5 发布后使用它了!你对此有什么看法?可以告诉我你的想法!