====== 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.