====== PHP RFC: Pipe Assignment Operator ======
* Version: 1.0.3
* Date: 2026-07-07
* Author: Caleb White, cdwhite3@pm.me
* Status: Under Discussion
* Target version: Next PHP 8.x (8.6)
* Implementation: https://github.com/php/php-src/pull/22633
* Discussion thread: https://news-web.php.net/php.internals/131849
* Voting thread: tbd
===== Introduction =====
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 !== '');
===== Proposal =====
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.
==== Variable Targets ====
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
==== Callable Styles ====
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
==== Expression Result ====
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
==== Single-Evaluation Guarantee ====
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.
==== Precedence and Associativity ====
''|>='' 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
==== Error Conditions ====
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
===== Examples =====
==== In-Place Transformation ====
// Before
$input = trim($input) |> strtolower(...);
// After
$input |>= trim(...) |> strtolower(...);
==== Sequential Processing ====
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(...);
==== Long Variable Names ====
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(...);
===== Backward Incompatible Changes =====
None. The token ''|>='' is currently a syntax error in all PHP versions.
===== Proposed PHP Version(s) =====
Next PHP 8.x (8.6).
===== RFC Impact =====
==== To the Ecosystem ====
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.
==== To Existing Extensions ====
The tokenizer extension gains a new ''T_PIPE_EQUAL'' constant. No other extensions are affected.
==== To SAPIs ====
No impact.
===== Open Issues =====
None.
===== Future Scope =====
* Any future changes to ''|>'' (e.g., new callable styles) would automatically apply to ''|>='' since they share the same callable dispatch logic in the compiler.
* Because ''|>='' has a dedicated AST node, the compiler knows at compile time that the variable is read and immediately overwritten. The [[https://wiki.php.net/rfc/implicit_move_optimisation|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).
===== Voting Choices =====
This RFC requires a 2/3 majority to pass.
* Yes
* No
* Abstain
===== Patches and Tests =====
Implementation: https://github.com/php/php-src/pull/22633
===== Implementation =====
After the RFC is implemented, this section will contain:
- the version(s) it was merged into
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
===== References =====
* [[https://wiki.php.net/rfc/pipe-operator-v3|PHP RFC: Pipe Operator v3]]
* [[https://wiki.php.net/rfc/null_coalesce_equal_operator|PHP RFC: Null Coalescing Assignment Operator]]
* [[https://wiki.php.net/rfc/implicit_move_optimisation|PHP RFC: Implicit Move Optimisation]]
* [[https://wiki.php.net/rfc/left-to-right-assignment-operator|PHP RFC: Left-to-right assignment operator (competing use of |>=)]]
===== Rejected Features =====
* **Alternative precedence**: Giving ''|>='' 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.
===== Changelog =====
* 1.0.3 (2026-07-20): PFA examples, single-evaluation clarification, added references.
* 1.0.2 (2026-07-14): Corrected precedence wording, added implicit move optimisation reference and future scope.
* 1.0.1 (2026-07-13): Added associativity and precedence examples.
* 1.0.0 (2026-07-09): Initial draft.