PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

PHP 8.5 RFC confirmed – pipe operator, readonly inheritance, array_first/last

by Henryk Tews / Tuesday, 15 April 2025 / Published in PHP

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.

About Henryk Tews

What you can read next

PHP 7.2 – object type hint, sodium instead of mcrypt, deprecations

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}