# RFC: Pipe Assignment Operator

- Status: Declined
- Authors: Caleb White, cdwhite3@pm.me
- Date: 2026-07-07
- Version: 1.0.3
- Target: Next PHP 8.x (8.6)
- Implementation: https://github.com/php/php-src/pull/22633
- Original: https://wiki.php.net/rfc/pipe_assignment_operator
- Page: https://externals.io/rfc/pipe_assignment_operator
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Implement the pipe assignment operator as outlined in the RFC? (closed)

Yes: 14 · No: 12 · Abstain: 7

Voters: alcaeus (Abstain), alexandredaubois (Abstain), as (No), beberlei (No), bmajdak (Yes), brzuchal (No), crell (No), cschneid (No), daniels (Abstain), derick (Abstain), dharman (Yes), duncan3dc (No), galvao (Yes), iliaa (Yes), imsop (Yes), jbnahan (Abstain), jordikroon (No), kalle (No), kguest (No), lstrojny (Yes), mbeccati (Yes), mcmic (No), ocramius (No), pajoye (Yes), ramsey (Yes), sebastian (Abstain), sergey (Yes), shivam (No), thekid (Yes), theodorejb (Yes), timwolla (Yes), trowski (Yes), weilindu (Abstain)

## Discussions on the mailing list

- [\[RFC\] Pipe Assignment Operator](https://externals.io/message/131849.md) — started on 2026-07-10, 38 messages
- [\[RFC\] \[Discussion\] Left-to-right assignment operator](https://externals.io/message/132024.md) — started on 2026-07-19, 1 messages
- [\[VOTE\] Pipe Assignment Operator](https://externals.io/message/132117.md) — started on 2026-07-28, 1 messages

---

## 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.):

```php
// 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:

```php
$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:

```php
$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):

```php
$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:

```php
$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:

```php
$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:

```php
$foo |>= bar(...) |> baz(...);
// is interpreted as:
$foo = (($foo |> bar(...)) |> baz(...));
```

Right-associativity with other assignment operators:

```php
// 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 `|>`:

```php
$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

```php
// Before
$input = trim($input) |> strtolower(...);
 
// After
$input |>= trim(...) |> strtolower(...);
```

### Sequential Processing

When transformations are conditional, sequential `|>=` reads naturally:

```php
// 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:

```php
// 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 [Implicit Move Optimisation](https://externals.io/rfc/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.

**Vote: Implement the pipe assignment operator as outlined in the RFC?** — Yes: 14 · No: 12 · Abstain: 7 (closed)

## Patches and Tests

Implementation: <https://github.com/php/php-src/pull/22633>

## Implementation

After the RFC is implemented, this section will contain:

1. the version(s) it was merged into
2. a link to the git commit(s)
3. a link to the PHP manual entry for the feature

## References

- [PHP RFC: Pipe Operator v3](https://externals.io/rfc/pipe-operator-v3)
- [PHP RFC: Null Coalescing Assignment Operator](https://externals.io/rfc/null_coalesce_equal_operator)
- [PHP RFC: Implicit Move Optimisation](https://externals.io/rfc/implicit_move_optimisation)
- [PHP RFC: Left-to-right assignment operator (competing use of \|&gt;=)](https://externals.io/rfc/left-to-right-assignment-operator)

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