PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

PHP 8.5 RC1 – pipe operator w realnym kodzie, pułapki, readonly dziedziczenie potwierdzone

by Henryk Tews / wtorek, 02 września 2025 / Opublikowano w PHP

PHP 8.5 RC1 wyszło w sierpniu 2025. Przez ostatnie tygodnie testowałem na kilku projektach. Pipe operator działa dokładnie tak jak RFC obiecywało – i zmienia sposób pisania przetwarzania danych bardziej niż jakikolwiek feature od czasu match() w PHP 8.0. Ale RC1 przyniosło też kilka niespodzianek których nie było w RFC drafcie. Raport z pierwszych tygodni.

Pipe Operator – pierwsze dwa tygodnie w kodzie

Zacząłem od refaktoringu starszego kodu przetwarzania danych importu. Wyniki mówią same za siebie:

<?php

declare(strict_types=1);

// Stary kod - trudny do czytania, wymaga śledzenia zagnieżdżeń
function processImportRow(array $rawRow): ?array
{
    return array_filter(
        array_combine(
            array_map('strtolower', array_keys($rawRow)),
            array_map('trim', array_values($rawRow))
        ),
        fn($v) => $v !== ''
    ) ?: null;
}

// PHP 8.5 - ten sam algorytm, czytelny kierunek przepływu
function processImportRow(array $rawRow): ?array
{
    return $rawRow
        |> array_combine(
            array_map('strtolower', array_keys($$)),
            array_map('trim', array_values($$))
           )
        |> array_filter($$, fn($v) => $v !== '')
        |> (fn($a) => $a ?: null)($$)
    ;
}
<?php

declare(strict_types=1);

// Szerszy przykład – pipeline przetwarzania zamówień
class OrderExportPipeline
{
    public function export(array $orders): string
    {
        return $orders
            |> array_filter($$, fn($o) => $o->getStatus() !== 'cancelled')
            |> array_map($$, fn($o) => $this->toExportDto($o))
            |> array_filter($$, fn($dto) => $dto->isValid())
            |> $this->sortByDate($$)
            |> $this->formatAsCsv($$)
        ;
    }

    private function sortByDate(array $dtos): array
    {
        usort($dtos, fn($a, $b) => $a->createdAt <=> $b->createdAt);
        return $dtos;
    }

    private function formatAsCsv(array $dtos): string
    {
        $lines = array_map(fn($dto) => implode(',', [
            $dto->orderId,
            $dto->total,
            $dto->createdAt->format('Y-m-d'),
        ]), $dtos);

        return implode("\n", array_merge(['id,total,date'], $lines));
    }

    private function toExportDto(object $order): object
    {
        // ... mapowanie
        return new \stdClass();
    }
}

Niespodzianki i pułapki z RC1

<?php

declare(strict_types=1);

// Pułapka 1: $$ to placeholder dla wartości z lewej strony pipe
// Nie wszędzie można go pominąć – zależy od oczekiwanej sygnatury funkcji

// OK - funkcja przyjmuje array jako pierwszy argument
$result = [1, 2, 3]
    |> array_reverse($$)      // array_reverse([1, 2, 3])
    |> array_sum($$)          // array_sum([3, 2, 1]) = 6
;

// OK - closure
$result = "hello"
    |> fn($s) => strtoupper($s)($$)  // Uwaga: closure z natychmiastowym wywołaniem
    |> strlen($$)
;

// Pułapka 2: metody na obiekcie wymagają closure lub ->method()
$order = new Order(42);

// To NIE działa bezpośrednio:
// $result = $order |> ->getStatus() // SyntaxError

// Obejście przez closure:
$result = $order
    |> fn($o) => $o->getStatus()($$)  // verbose ale działa
;

// Pułapka 3: void funkcje w pipe
// array_walk modyfikuje in-place i zwraca bool, nie tablicę
// Nie używaj void funkcji w środku pipeline

// Pułapka 4: null safety
// Pipe nie ma wbudowanego short-circuit dla null
// Jeśli któryś krok zwróci null i następny tego nie obsługuje – TypeError
$result = null
    |> trim($$)    // TypeError: trim(): Argument #1 must be of type string, null given
;

// Ochrona:
$result = $maybeNull
    |> ($$ !== null ? trim($$) : null)
    |> ($$ !== null ? strtoupper($$) : null)
;

Readonly dziedziczenie – działa zgodnie z RFC

<?php

declare(strict_types=1);

// Potwierdzenie z RC1 - readonly class może dziedziczyć po readonly class
readonly class Money
{
    public function __construct(
        public int $amount,       // w groszach
        public string $currency
    ) {}

    public function add(Money $other): static
    {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException('Currency mismatch');
        }
        return new static($this->amount + $other->amount, $this->currency);
    }
}

// PHP 8.5 - OK!
readonly class TaxedMoney extends Money
{
    public function __construct(
        int $amount,
        string $currency,
        public float $taxRate
    ) {
        parent::__construct($amount, $currency);
    }

    public function netAmount(): int
    {
        return (int) round($this->amount / (1 + $this->taxRate));
    }

    public function taxAmount(): int
    {
        return $this->amount - $this->netAmount();
    }
}

readonly class DiscountedMoney extends Money
{
    public function __construct(
        int $amount,
        string $currency,
        public float $discountPercent
    ) {
        parent::__construct($amount, $currency);
    }

    public function originalAmount(): int
    {
        return (int) round($this->amount / (1 - $this->discountPercent / 100));
    }
}

$price    = new Money(12300, 'PLN');              // 123.00 PLN
$taxed    = new TaxedMoney(12300, 'PLN', 0.23);   // 123.00 PLN z 23% VAT
$discount = new DiscountedMoney(12300, 'PLN', 10); // 123.00 PLN po 10% rabacie

echo $taxed->netAmount();     // 10000 (100.00 PLN netto)
echo $discount->originalAmount(); // 13667 (136.67 PLN przed rabatem)

Nowe deprecacje w RC1

<?php

// Deprecacje które pojawiły się w RC1 a nie było ich w early RFC

// 1. zero-argument implode() deprecated
implode($array); // Deprecated - zawsze podawaj separator
implode(',', $array); // Poprawnie

// 2. Niektóre warianty preg_ z null w pattern
preg_match(null, $string); // TypeError (wcześniej E_WARNING)

// 3. Implicit float->int conversion w tablicach
$arr = [];
$arr[1.5] = 'value'; // Deprecated: float 1.5 truncated to int key 1

// Sprawdzenie projektu przez PHPStan
// vendor/bin/phpstan analyse --php-version=8.5 --level=8 src/

Benchmark – czy pipe operator wpływa na wydajność?

<?php

// Krótka odpowiedź: minimalny wpływ – pipe operator to syntactic sugar
// kompilowany do identycznych opcodes co zagnieżdżone wywołania

$data = range(1, 10000);

// Wersja zagnieżdżona
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    $result1 = array_sum(array_filter(array_map(fn($x) => $x * 2, $data), fn($x) => $x % 3 === 0));
}
$nested = (microtime(true) - $start) * 1000;

// Wersja z pipe
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    $result2 = $data
        |> array_map(fn($x) => $x * 2, $$)
        |> array_filter($$, fn($x) => $x % 3 === 0)
        |> array_sum($$)
    ;
}
$piped = (microtime(true) - $start) * 1000;

echo "Zagnieżdżone: {$nested}ms\n";  // np. 142ms
echo "Pipe:         {$piped}ms\n";   // np. 143ms - identyczne

// Opcodes są niemal identyczne - pipe to wygoda, nie optymalizacja

Kiedy spodziewać się final release

# Harmonogram PHP 8.5 (szacowany)
# RC1:     sierpień 2025
# RC2:     wrzesień 2025
# RC3:     październik 2025
# Final:   6 lub 20 listopada 2025

# Testuj już teraz na RC
docker pull php:8.5-rc-cli
docker run --rm -v $(pwd):/app php:8.5-rc-cli php /app/test_85.php

# Composer z PHP 8.5 RC (ignoruj platform requirements)
docker run --rm -v $(pwd):/app php:8.5-rc-cli \
    composer install --ignore-platform-reqs

Podsumowanie

PHP 8.5 RC1 potwierdza że pipe operator będzie jedną z największych ergonomicznych zmian PHP od lat. Kod przetwarzania danych który pisałem z wieloma zmiennymi tymczasowymi albo głębokim zagnieżdżeniem staje się linearny i czytelny. Readonly dziedziczenie dopełnia Value Object toolkit. RC1 jest stabilne – warto już testować projekty pod kątem nowych deprecacji, żeby final release w listopadzie nie zaskoczył.

About Henryk Tews

Co możesz przeczytać następne

REST API – zasoby vs akcje, kody HTTP, struktura odpowiedzi, wersjonowanie
PHP 7.4 w praktyce – pułapki typed properties, hydratacja, zapowiedź PHP 8.0
Komponenty Symfony bez frameworka – Console, Validator, HttpClient
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
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 Zawsze aktywne
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.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}