The PHP 8.5 RFC vote on the pipe operator passed. After years of debate it is officially coming in November 2025. Readonly property inheritance also passed – child classes can now override parent readonly properties in specific cases. array_first() and array_last() join the array function family. I show what the final syntax looks like and how it changes real code.
Pipe operator – confirmed syntax
<?php
declare(strict_types=1);
// The pipe operator passes the left-hand value as the first argument
// to the right-hand callable
// Basic usage
$result = " Hello World "
|> trim(...)
|> strtolower(...)
|> str_replace(' ', '-', ...);
// Result: 'hello-world'
// With partial application using ... spread notation
// The value goes to the FIRST argument
$slugify = fn(string $s): string => $s
|> trim(...)
|> strtolower(...)
|> fn($x) => preg_replace('/[^a-z0-9]+/', '-', $x)
|> trim(..., '-');
echo $slugify(' Hello / World '); // 'hello-world'
// Collection pipeline - the main use case
$totalRevenue = $orders
|> array_filter(..., fn($o) => $o['status'] === 'complete')
|> array_map(..., fn($o) => $o['grand_total'])
|> array_sum(...);
// With named functions and methods
$processed = $rawData
|> array_filter(...)
|> $this->validateItems(...)
|> $this->transformItems(...)
|> array_values(...);
Pipe operator with closures – practical patterns
<?php
declare(strict_types=1);
// Building pipelines for import/transform operations
class ProductTransformer
{
public function transform(array $rawProducts): array
{
return $rawProducts
|> $this->filterActive(...)
|> $this->normaliseSkus(...)
|> $this->calculateMargins(...)
|> $this->sortByMargin(...)
|> array_values(...);
}
private function filterActive(array $products): array
{
return array_filter($products, fn($p) => $p['status'] === 1);
}
private function normaliseSkus(array $products): array
{
return array_map(fn($p) => [
...$p,
'sku' => strtoupper(trim($p['sku'])),
], $products);
}
private function calculateMargins(array $products): array
{
return array_map(fn($p) => [
...$p,
'margin' => $p['price'] - ($p['cost'] ?? 0),
], $products);
}
private function sortByMargin(array $products): array
{
usort($products, fn($a, $b) => $b['margin'] <=> $a['margin']);
return $products;
}
}
// Without pipe - same logic, harder to read
$result = array_values(
$this->sortByMargin(
$this->calculateMargins(
$this->normaliseSkus(
$this->filterActive($rawProducts)
)
)
)
);
Readonly property inheritance
<?php
declare(strict_types=1);
// PHP 8.4 - child class cannot redeclare a parent's readonly property
class BaseProduct
{
public function __construct(
public readonly int $id,
public readonly string $sku,
) {}
}
class SimpleProduct extends BaseProduct
{
// PHP 8.4: ERROR - Cannot redeclare readonly property BaseProduct::$sku
// public readonly string $sku = '';
}
// PHP 8.5 - child class CAN redeclare if keeping readonly
// (subject to final RFC text - vote passed, exact rules to confirm)
class SimpleProduct extends BaseProduct
{
public function __construct(
int $id,
string $sku,
public readonly float $price, // new readonly property in child
public readonly float $weight = 0.0,
) {
parent::__construct($id, $sku);
}
}
// This was already possible in PHP 8.4:
readonly class BaseDto
{
public function __construct(
public int $id,
public string $type,
) {}
}
// PHP 8.5 cleanup: makes readonly class hierarchies more ergonomic
readonly class ProductDto extends BaseDto
{
public function __construct(
int $id,
string $type,
public string $sku,
public float $price,
) {
parent::__construct($id, $type);
}
}
array_first() and array_last()
<?php
// Before PHP 8.5 - getting first/last element safely
$first = $items[array_key_first($items)] ?? null;
$last = $items[array_key_last($items)] ?? null;
// Or:
reset($items); $first = current($items);
end($items); $last = current($items);
// PHP 8.5 - clean, intention-revealing
$first = array_first($items); // first element or null for empty array
$last = array_last($items); // last element or null for empty array
// Also accepts a predicate (filter + first in one call)
$firstExpensive = array_first($products, fn($p) => $p['price'] > 100);
// Equivalent to array_find() but emphasises "first matching" semantics
// Practical example
$latestOrder = array_first(
array_filter($orders, fn($o) => $o['status'] === 'processing')
);
What did NOT make it into 8.5
Generics: RFC informational only, no vote planned for 8.5 Clone-with syntax: vote passed BUT implementation pushed to 8.6 (complex edge cases) Pattern matching: still in RFC discussion stage array_zip(): RFC in drafting, vote pending
Summary
PHP 8.5’s pipe operator is the most significant syntax addition since the match expression in PHP 8.0. It transforms deeply nested function calls into readable left-to-right pipelines – exactly what collection processing and data transformation code needs. Readonly property inheritance improvements make value object hierarchies cleaner. array_first() and array_last() are small but welcome additions that eliminate a common awkward idiom. PHP 8.5 ships November 2025 – start watching the RC builds.
