PHP 8.5 introduced the pipe operator (|>), enabling functional-style transformation chains. A common pattern is piping a variable through transformations and assigning the result back to the same variable.
This RFC proposes |>=, a compound assignment form that eliminates the repetition, consistent with PHP's existing compound assignment operators (+= , .=, ??=, etc.):
// Before $this->requestData = array_filter($this->requestData); // or $this->requestData = $this->requestData |> array_filter(...); $headers = $headers |> array_change_key_case(?, CASE_LOWER) |> array_filter(?, fn($v) => $v !== ''); // After $this->requestData |>= array_filter(...); $headers |>= array_change_key_case(?, CASE_LOWER) |> array_filter(?, fn($v) => $v !== '');
The expression $x |>= callable is equivalent to $x = $x |> callable.
When the right-hand side contains a pipe chain, the variable is spliced into the leftmost position:
$x |>= fn1(...) |> fn2(...) |> fn3(...); // Equivalent to: $x = $x |> fn1(...) |> fn2(...) |> fn3(...);
This works because |> has higher precedence than |>=. The parser groups the RHS as a pipe chain, and the compiler flattens it, inserting the LHS variable as the initial input.
All variable types that support compound assignment are supported:
$x |>= strtoupper(...); // Simple variable $data["key"] |>= strtoupper(...); // Array dimension $obj->name |>= strtoupper(...); // Object property Foo::$value |>= strtoupper(...); // Static property
All callable styles supported by |> work with |>= (including PFA, which eliminates the need for closure wrappers):
$x |>= strtoupper(...); // First-class callable $x |>= Formatter::normalize(...); // Static method $x |>= $formatter->normalize(...); // Instance method $x |>= (fn($v) => $v * 2); // Arrow function (must be parenthesized) $x |>= $storedCallable; // Stored callable $x |>= str_replace('a', 'b', ?); // Partial function application
Like other compound assignment operators, |>= produces the assigned value as its expression result:
$x = 5; $y = ($x |>= (fn($v) => $v * 3)); // $x === 15, $y === 15
When the LHS contains sub-expressions, they are evaluated exactly once:
$arr[expensive_call()] |>= strtoupper(...); // expensive_call() is invoked only once
With the manual $x = $x |> ... pattern, sub-expressions are evaluated twice: once on each side of the assignment. |>= is both more correct and more performant.
This is the same guarantee that ??= provides over $x = $x ?? default, implemented using the same compile-time memoization mechanism.
Note: for chained property access like $a->b->c |>= strtoupper(...), intermediate fetches (e.g. $a->b) are evaluated once for the read and once for the write-back. This is consistent with ??= and all other compound assignment operators.
|>= has the same precedence and associativity as all other assignment operators (=, += , .=, ??=, etc.): right-associative, binding tighter than the keyword operators (and, or, xor, yield, print, etc.) but looser than all other operators.
Because |> binds tighter than |>=, pipe chains on the RHS are grouped first. The compiler then splices the LHS variable into the chain. For example:
$foo |>= bar(...) |> baz(...); // is interpreted as: $foo = (($foo |> bar(...)) |> baz(...));
Right-associativity with other assignment operators:
// Nested |>=: right-to-left evaluation $foo |>= $bar |>= double(...); // is interpreted as: $foo |>= ($bar |>= double(...)); // 1. $bar = double($bar), expression result is the new $bar // 2. $foo = ($foo |> new_$bar), i.e., new_$bar is used as the callable // Mixed with other compound assignments $foo += $bar |>= triple(...); // is interpreted as: $foo += ($bar |>= triple(...)); // 1. $bar = triple($bar), expression result is the new $bar // 2. $foo += new_$bar // |>= result used with another operator $result = $x |>= double(...) or die('failed'); // is interpreted as: ($result = ($x |>= double(...))) or die('failed'); // because `or` has lower precedence than assignment
Consistent with |>:
$x |>= fn($v) => $v * 2; // Fatal error: must be parenthesized $this |>= trim(...); // Fatal error: Cannot re-assign $this getVal() |>= strtoupper(...); // Fatal error: Can't use function return value in write context $x |>= sort(...); // Error: Argument #1 could not be passed by reference
// Before $input = trim($input) |> strtolower(...); // After $input |>= trim(...) |> strtolower(...);
When transformations are conditional, sequential |>= reads naturally:
// Before $this->user->displayName = trim($this->user->displayName); if ($normalize) { $this->user->displayName = strtolower($this->user->displayName); } $this->user->displayName = ucfirst($this->user->displayName); // After $this->user->displayName |>= trim(...); if ($normalize) { $this->user->displayName |>= strtolower(...); } $this->user->displayName |>= ucfirst(...);
The value of |>= scales with variable complexity:
// Before $this->currentOrder->lineItems = array_unique($this->currentOrder->lineItems) |> array_values(...) // After $this->currentOrder->lineItems |>= array_unique(...) |> array_values(...);
None. The token |>= is currently a syntax error in all PHP versions.
Next PHP 8.x (8.6).
IDEs and static analyzers that support |> would need to add support for |>=. The semantics are identical to $x = $x |> ..., so type inference requires no new logic.
The tokenizer extension gains a new T_PIPE_EQUAL constant. No other extensions are affected.
No impact.
None.
|> (e.g., new callable styles) would automatically apply to |>= since they share the same callable dispatch logic in the compiler.|>= has a dedicated AST node, the compiler knows at compile time that the variable is read and immediately overwritten. The Implicit Move Optimisation RFC proposes an opcache optimization for exactly this $x = func($x) pattern, avoiding unnecessary copy-on-write copies. |>= would benefit from that optimization automatically, and could potentially emit the implicit-move flag directly at compile time without requiring opcache's SSA analysis (an optimization the manual $x = $x |> ... form cannot support).This RFC requires a 2/3 majority to pass.
Implementation: https://github.com/php/php-src/pull/22633
After the RFC is implemented, this section will contain:
|>= the same precedence as |> was considered, but $x |>= fn1(...) |> fn2(...) would parse as ($x |>= fn1(...)) |> fn2(...), where only fn1's result is assigned and fn2's result is discarded. Assignment-level precedence with compile-time chain flattening produces the expected behavior.