编程

PHP 8.5: 管道操作符 (|>)

30 2025-07-16 08:18:00

PHP 8.5 添加了一个新的操作符:管道操作符(|>),用以从左到右链式调用多个 callable,并将左边 callable 返回的值传入到右边。

管道操作符并不会为语言添加新的能力。它以更直观的方式“链式调用”多个 callable,而不必像以前那样嵌套调用多个callable 或者使用变量获取返回值再传入下一个 callable。

$result = "Hello World"
    |> strtoupper(...)
    |> str_shuffle(...)
    |> trim(...);

// "LWHO LDLROE"

上述代码使用了新增的管道操作符(|>),将前一个 callable 的返回值作为参数传入后一个 callable 中。 函数后面的

 (...) 使之成为 first-class callable。

以下两段代码显示的是不使用管道操作符如何得到相同的结果:

使用嵌套调用

$result = trim(str_shuffle(strtoupper("Hello World")));

使用变量

$result = "Hello World";
$result = strtoupper($result);
$result = str_shuffle($result);
$result = trim($result);

写时拷贝

当使用变量来持有另外一个函数调用的返回值时,PHP 使用写时拷贝(copy-on-write),这意味着即使创建了新的变量,在变量被写之前,它不会消耗额外的系统内存。虽然管道操作符提供了更为简洁的代码书写方式,使用临时变量也未必会消耗更多的资源。

虽然使用换行可以让管道操作符代码更为清晰易读,但换行并非必需的。下面的示例代码也是完全有效合法的:

$result = strtoupper("Hello World") 

管道操作符用例

管道操作符将左边函数的返回值传递给右边的函数。当 PHP 脚本编译之后,管道调用的调用链将被优化,并产生类似于嵌套调用的操作码(opcode)。

不过,管道操作符也有一些限制。当链条中所有函数都只需要一个参数、有返回值且不接受参数引用时,适合使用它。

管道操作符接受任何的 callable

管道操作符链路中的 callable 可以是任何形式的 callable。比如,可以将用户空间函数、PHP 内置函数、静态类方法、lambda 函数、箭头函数、实现 __invoke__ 魔术方法的类以及 first-class callable 一起混合使用。

$result = "Hello World"
    |> 'strtoupper'
    |> str_shuffle(...)
    |> fn($x) => trim($x)
    |> function(string $x): string {return strtolower($x);}
    |> new MyClass()
    |> [MyClass::class, 'myStaticMethod']
    |> new MyClass()->myInstanceMethod(...)
    |> my_function(...);

echo $result;

function my_function(string $x): string {
    return substr($x, 0, 10);
}

class MyClass {
    public function __invoke(string $x): string {
        return str_rot13($x);
    }

    public function myInstanceMethod(string $x): string {
        return hash('sha256', $x);
    }

    public static function myStaticmethod(string $x): string {
        return str_replace('E', 'O', $x);
    }
}

此外,它也可以是返回 callable 的表达式:

$result = "Hello World"
    |> 'strtoupper'
    |> get_callable();

echo $result; // 787ec76dcafd20c1908eb0936a12f91edd105ab5cd7ecc2b1ae2032648345dff

function get_callable(): callable {
    return fn($x) => hash('sha256', $x);
}

只接受接收第一个参数的 callable

管道操作符的一个主要限制是,所有链条中的 callable 

必须只有一个必需的参数。

对于内置函数,如果该函数不接受任何参数,它不能用在链式调用中。对于用户空间 PHP 函数,将一个参数传入到不接受任何参数的函数中,不会导致任何错误,只是被静默忽略。

使用管道操作符,前一个表达式或者 callable 的返回值总是作为下一个 callable 的第一个参数传递。你无法修改参数的位置

类型强制转换

当传递值到下一个 callable 时,管道操作符不会修改 PHP 的强制类型转换的实现方式。

strict_mode 启用时,它要求类型严格匹配。

declare(strict_types = 1);
$result = 1 |> strlen(...);
TypeError: strlen(): Argument #1 ($string) must be of type string, int given in ...

返回类型为 void 的 callable

返回类型为 void 的 PHP 函数/callable 也可以在链中调用,不过它们的返回值将被强制转换为 nullvoid 函数通常用在链式调用的最后一个 callable 而不是用在中间,因为此后的链路只会得到 null。 

使用引用参数的函数

由于管道的中间值不存储在通常的变量中,因此使用期望参数通过引用传递的函数将不被允许。

explode("-", 'a-b-c') |> array_pop(...);
Error: array_pop(): Argument #1 ($array) could not be passed by reference in

不过,这个规则有一个例外:即内置的 PHP 函数可以使用 @prefer-ref 声名函数。传递直接值到这些函数不会导致错误。

截至 PHP 8.5,PHP 内核中只有两个这样的函数。FFI 扩展的  FFI 类也有一些方法支持该声名,不过它们不大可能使用管道操作符调用。

explode("-", 'a-b-c') |> array_multisort(...);
// true
['foo' => 'hello', 'bar' => 2] |> extract(...);
echo $foo; // hello

上述代码使用了 PHP 内核中唯二使用 @prefer-ref 引用的两个函数:array_multisortextract

操作符优先级

callable 总是从左到右依次调用,除非使用 () 对优先级进行了更改。

echo 10 + 6 |> dechex(...) |> hexdec(...);
// 16

上述代码中,10 + 6 最先进行了计算,然后将值传入后面的链路中。 

类似于 PHP 中的其他操作符,使用 () 允许改变优先级顺序:

echo 10 + (6 |> dechex(...)) |> hexdec(...);
// 22

当使用 null 断值或者三元操作符时,其优先级也是从左到右:

echo 10 ?? 6 |> dechex(...) |> hexdec(...);
// 10

echo null ?? 6 |> dechex(...) |> hexdec(...);
// 6
echo true ? 5 : 6 |> dechex(...) |> hexdec(...);
// 5

当管道操作符用做三元表达式或者 null 断值的左边表达式时管道优先执行:

echo 10 + 6 |> dechex(...) |> hexdec(...) === 16
 ? 'is sixteen'
 : 'not sixteen';
 // is sixteen

等号操作符 (=====) 的优先级较低,因此像通常期待那样工作:

10 + 6 |> dechex(...) |> hexdec(...) === 16
// true

16 = 10 + 6 |> dechex(...) |> hexdec(...) 
// true

向后兼容性影响

管道操作符是一个新的语法,因此无法对 PHP 8.4 及其之前的版本提供向后兼容性。

老版本中尝试运行使用管道操作符的 PHP 代码,将会导致解析错误:

Parse error: syntax error, unexpected token ">" in ...