PHP 8.5: Оператор pipe (|>)

В PHP 8.5 добавлен новый оператор, оператор pipe (|>), позволяющий связывать несколько вызываемых объектов слева направо, принимая возвращаемое значение левого вызываемого объекта и передавая его вправо.

Что такое оператор pipe в PHP 8.5

Оператор конвейера pipe (|>) не добавляет новых возможностей в язык. Тем не менее, он может помочь «связать» несколько вызываемых объектов более наглядным способом, вместо того, чтобы вкладывать их друг в друга или использовать переменную для хранения возвращаемых значений и их передачи.

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

// "LWHO LDLROE"

В приведённом выше фрагменте кода используется новый оператор pipe (|>) для указания того, что возвращаемое значение предыдущего вызываемого объекта должно быть передано вызываемому объекту справа. Часть (...) рядом с именем функции делает её вызываемым объектом первого класса.

Следующие два фрагмента кода показывают, как достичь того же результата без использования оператора pipe:

Использование вложенных вызовов
$result = trim(str_shuffle(strtoupper("Hello World")));
Использование переменной
$result = "Hello World";
$result = strtoupper($result);
$result = str_shuffle($result);
$result = trim($result);

Хотя это может снизить ясность кода, оператор |> не обязательно должен находиться в новой строке. Следующий пример является полностью допустимым синтаксисом:

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

Использование оператора pipe

Оператор pipe передаёт возвращаемое значение левой функции правой функции. При компиляции скрипта PHP цепочки вызовов |> оптимизируются и приводят к появлению опкодов, аналогичных типичным вложенным вызовам.

Однако оператор конвейера pipe имеет ряд ограничений. Он подходит для случаев, когда все функции в цепочке требуют только один параметр, имеют возвращаемые значения и не принимают параметры по ссылке.

Оператор pipe принимает любой callable объект

Вызываемые объекты в цепочке операторов pipe могут быть любыми callable объектами. Например, можно смешивать пользовательские функции, встроенные функции, статические методы классов, лямбда-функции, стрелочные функции, классы, реализующие магический метод __invoke, и вызываемые объекты первого класса.

$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);
}

Принимает только вызываемые объекты, требующие только первый параметр

Основным ограничением оператора pipe является то, что все вызываемые объекты в цепочке должны принимать только один обязательный параметр.

Для встроенных функций, если функция не принимает никаких параметров, она не может быть использована в цепочке преобразования. Для пользовательских PHP функций передача параметра функции, не принимающей никаких параметров, не вызывает ошибку и игнорируется без предупреждения.

При использовании оператора pipe возвращаемое значение предыдущего выражения или вызываемого объекта всегда передаётся в качестве первого параметра следующему вызываемому объекту. Изменить положение параметра невозможно.

Принудительное преобразование типов

Оператор pipe не изменяет способ принудительного преобразования типов в PHP при передаче значения следующему вызываемому объекту.

Когда strict_mode включён, он требует строгого соответствия типов.

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

Вызываемые объекты с возвращаемым типом void

Функции/вызываемые объекты PHP с возвращаемым типом void могут использоваться в цепочке преобразования, но возвращаемое значение принудительно преобразуется в null. Возвращающая void функция обычно используется в качестве последнего callable объекта цепочки, а не в середине, поскольку остальная часть цепочки с этого момента будет получать только null.

Функции с параметрами по ссылке

Поскольку промежуточные значения оператора pipe не хранятся в типичной переменной, использование функций, ожидающих передачу параметра по ссылке, не допускается.

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 содержит несколько методов, поддерживающих эту функцию, но они вряд ли будут использоваться с оператором pipe.

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

В приведённых выше примерах оператора pipe используются array_multisort и extract — единственные две функции в ядре PHP, использующие ссылки @prefer-ref.

Приоритет операторов

Вызываемые функции всегда вызываются слева направо, если их порядок не изменён с помощью () круглых скобок.

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

В приведённом выше примере оператора pipe сначала вычисляется 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

Когда оператор конвейера pipe используется в качестве левого выражения тернарного оператора или оператора объединения с null, сначала выполняется оператор pipe:

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

Операторы равенства (== и ===) имеют более низкий приоритет и, следовательно, работают как ожидается:

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

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

Влияние на обратную совместимость

Оператор pipe является новым синтаксисом и не совместим с PHP 8.4 и более старыми версиями.

Попытка запустить PHP-код, использующий оператор pipe, приведёт к ошибке парсинга в более старых версиях PHP:

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

Комментарии


Дополнительные материалы

Предыдущая Статья

Цикл релизов PHP: обновления, сроки поддержки и изменения

Следующая Статья

PHP 8.5: новое поведение Filter — исключения при сбоях валидации