PHP 8.5: Toán tử Pipe (|>) - Tính năng mới đáng chú ý

PHP 8.5 giới thiệu một toán tử mới mang tên pipe operator (|>) nhằm đơn giản hóa việc liên kết (chain) các hàm hoặc callable theo thứ tự từ trái sang phải. Toán tử này cho phép lấy giá trị trả về của hàm bên trái và truyền nó trực tiếp làm tham số cho hàm bên phải. Mặc dù không bổ sung khả năng mới cho ngôn ngữ, toán tử pipe giúp mã nguồn trở nên rõ ràng và dễ đọc hơn, thay vì phải lồng ghép nhiều hàm hoặc sử dụng biến tạm để lưu trữ giá trị trung gian.


Cú pháp và cách sử dụng cơ bản

Toán tử pipe (|>) cho phép liên kết các callable một cách trực quan. Dưới đây là một ví dụ minh họa:

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

Trong đoạn mã trên:

  • Chuỗi "Hello World" được truyền qua hàm strtoupper để chuyển thành chữ hoa, kết quả là "HELLO WORLD".
  • Kết quả này tiếp tục được truyền vào str_shuffle để xáo trộn các ký tự, ví dụ: "LWHO LDLROE".
  • Cuối cùng, hàm trim được gọi để loại bỏ khoảng trắng (nếu có).

Phần ... trong cú pháp biểu thị rằng hàm được sử dụng như một first-class callable, tức là một tham chiếu đến hàm có thể được truyền hoặc sử dụng trực tiếp.


So sánh với cách viết truyền thống

Trước khi có toán tử pipe, để đạt được kết quả tương tự, bạn có thể phải viết mã theo một trong hai cách sau:

1. Sử dụng lồng ghép hàm:

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

Cách này tuy ngắn gọn nhưng có thể khó đọc khi chuỗi hàm trở nên phức tạp, đặc biệt với nhiều hàm lồng nhau.

2. Sử dụng biến tạm:

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

Cách này rõ ràng hơn nhưng dài dòng và yêu cầu tạo biến tạm để lưu trữ giá trị trung gian.

Toán tử pipe giúp mã nguồn trở nên gọn gàng và dễ hiểu hơn, đặc biệt khi cần thực hiện nhiều thao tác liên tiếp.


Cú pháp inline

Toán tử pipe không bắt buộc phải xuống dòng. Ví dụ, đoạn mã sau là hoàn toàn hợp lệ:

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

Tuy nhiên, việc viết trên cùng một dòng có thể làm giảm tính rõ ràng của mã, vì vậy nên cân nhắc sử dụng xuống dòng để dễ đọc hơn.


Các đặc điểm chính của toán tử Pipe

1. Hỗ trợ mọi loại callable

Toán tử pipe chấp nhận bất kỳ callable nào trong PHP, bao gồm:

Hàm tích hợp sẵn (built-in functions) như strtoupper, str_shuffle.

Hàm do người dùng định nghĩa (user-land functions).

Phương thức tĩnh của lớp (static class methods).

Hàm lambda hoặc arrow function.

Các lớp triển khai phương thức __invoke.

  • Phương thức của instance hoặc first-class callable.

Ví dụ:

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

Thậm chí, bạn có thể sử dụng biểu thức trả về một callable:

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

echo $result; // 787ec76dcafd20c1908eb0936a12f91edd105ab5cd7ecc2b1ae2032648345dff

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

2. Hạn chế về tham số

Toán tử pipe chỉ phù hợp khi tất cả các callable trong chuỗi chỉ yêu cầu một tham số bắt buộc và không chấp nhận tham số truyền theo tham chiếu (by-reference). Giá trị trả về của callable trước luôn được truyền làm tham số đầu tiên cho callable tiếp theo, và không thể thay đổi vị trí của tham số này.

Ví dụ, các hàm không nhận tham số (như phpinfo()) không thể sử dụng trong chuỗi pipe. Đối với các hàm do người dùng định nghĩa, nếu hàm không yêu cầu tham số, PHP sẽ bỏ qua tham số được truyền mà không gây lỗi.


3. Ép kiểu (Type Coercion)

Toán tử pipe không thay đổi cách PHP xử lý ép kiểu dữ liệu. Khi chế độ strict_types được bật, các kiểu dữ liệu phải khớp chính xác, nếu không sẽ gây lỗi. Ví dụ:

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

4. Hàm với kiểu trả về void

Các hàm có kiểu trả về void có thể được sử dụng trong chuỗi pipe, nhưng giá trị trả về sẽ được ép thành null. Do đó, các hàm này thường chỉ nên được sử dụng ở cuối chuỗi, vì các callable tiếp theo sẽ chỉ nhận được null.


5. Tham số truyền tham chiếu (by-reference)

Do giá trị trung gian trong chuỗi pipe không được lưu trữ trong biến thông thường, các hàm yêu cầu tham số truyền tham chiếu sẽ gây lỗi. Ví dụ:

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

Tuy nhiên, một số hàm tích hợp sẵn trong PHP core, như array_multisortextract, được đánh dấu là @prefer-ref, cho phép truyền giá trị trực tiếp mà không gây lỗi:

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

6. Độ ưu tiên của toán tử

Toán tử pipe được thực thi từ trái sang phải, trừ khi sử dụng dấu ngoặc () để thay đổi thứ tự. Ví dụ:

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

Nếu muốn thay đổi thứ tự thực thi:

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

Toán tử pipe có độ ưu tiên cao hơn các toán tử so sánh (==, ===), nhưng thấp hơn toán tử cộng (+). Khi sử dụng với toán tử null coalesce (??) hoặc toán tử ba ngôi (?:), chuỗi pipe được thực thi trước:

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

7. Tối ưu hóa hiệu suất

Khi biên dịch mã PHP, các chuỗi pipe được tối ưu hóa để tạo ra các opcode tương tự như khi sử dụng các hàm lồng ghép thông thường. Điều này đảm bảo rằng toán tử pipe không gây ra chi phí hiệu suất đáng kể.

Hơn nữa, khi sử dụng biến tạm để lưu trữ giá trị trung gian, PHP áp dụng cơ chế copy-on-write, nghĩa là không tiêu tốn thêm bộ nhớ hệ thống cho đến khi giá trị của biến được thay đổi. Vì vậy, toán tử pipe không nhất thiết tiết kiệm tài nguyên hơn so với việc sử dụng biến tạm, nhưng nó mang lại mã nguồn sạch sẽ và dễ bảo trì hơn.


Tính tương thích ngược

Toán tử pipe là một cú pháp mới, không tương thích ngược với các phiên bản PHP 8.4 trở về trước. Nếu chạy mã sử dụng toán tử pipe trên các phiên bản cũ, bạn sẽ gặp lỗi cú pháp:

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

Kết luận

Toán tử pipe (|>) trong PHP 8.5 là một bổ sung đáng giá, giúp cải thiện tính rõ ràng và ngắn gọn của mã nguồn khi cần liên kết nhiều callable. Mặc dù không mang lại khả năng mới, nó cung cấp một cách tiếp cận trực quan hơn so với lồng ghép hàm hoặc sử dụng biến tạm. Tuy nhiên, lập trình viên cần lưu ý các hạn chế, chẳng hạn như chỉ hỗ trợ callable với một tham số bắt buộc và không hỗ trợ tham số truyền tham chiếu. Với cú pháp đơn giản và khả năng tối ưu hóa hiệu suất, toán tử pipe hứa hẹn sẽ trở thành một công cụ hữu ích trong việc viết mã PHP hiện đại.