Zbliżamy się do Feature Freeze PHP 8.5 (latem 2025). Kilka RFC przeszło głosowanie i jest już w masterze. Przeglądam co jest potwierdzone, co jest w ostatniej rundzie głosowania i które propozycje nie zebrały wymaganego 2/3 głosów. Bez spekulacji – tylko potwierdzone zmiany.
Potwierdzone – RFC przeszły głosowanie
1. Pipe Operator – w końcu!
Po latach dyskusji i kilku nieudanych próbach, RFC dla pipe operatora przeszło głosowanie w marcu 2025. Składnia |> z closures:
<?php
declare(strict_types=1);
// PHP 8.5 - pipe operator
// Wartość po lewej jest przekazywana jako pierwszy argument do callable po prawej
$result = " hello world "
|> trim(...) // trim(' hello world ') = 'hello world'
|> strtoupper(...) // strtoupper('hello world') = 'HELLO WORLD'
|> str_word_count(...) // str_word_count('HELLO WORLD') = 2
;
echo $result; // 2
// Z closure dla bardziej złożonych operacji
$processedSkus = $rawInput
|> trim(...)
|> strtoupper(...)
|> fn($s) => preg_replace('/[^A-Z0-9\-]/', '', $s)
|> fn($s) => strlen($s) >= 3 ? $s : null
;
// Bardzo czytelne przetwarzanie kolekcji
$activeSkus = $products
|> array_filter(..., fn($p) => $p->isActive())
|> array_map(..., fn($p) => $p->getSku())
|> array_unique(...)
|> array_values(...)
;
// Zastępuje zagnieżdżone wywołania lub zmienne tymczasowe
// Przed:
$result = array_values(array_unique(array_map(
fn($p) => $p->getSku(),
array_filter($products, fn($p) => $p->isActive())
)));
2. Readonly Klasy z Dziedziczeniem
<?php
declare(strict_types=1);
// PHP 8.5 - readonly class może dziedziczyć po readonly class
readonly class Address
{
public function __construct(
public string $street,
public string $city,
public string $postcode
) {}
public function format(): string
{
return "{$this->street}, {$this->postcode} {$this->city}";
}
}
// PHP 8.4: Fatal Error - nie można dziedziczyć readonly po readonly
// PHP 8.5: OK - obie klasy są readonly
readonly class InternationalAddress extends Address
{
public function __construct(
string $street,
string $city,
string $postcode,
public string $country = 'PL',
public string $countryCode = 'PL'
) {
parent::__construct($street, $city, $postcode);
}
public function format(): string
{
return parent::format() . ", {$this->country}";
}
}
readonly class BillingAddress extends Address
{
public function __construct(
string $street,
string $city,
string $postcode,
public string $companyName = '',
public string $vatNumber = ''
) {
parent::__construct($street, $city, $postcode);
}
}
$address = new InternationalAddress(
street: 'ul. Marszałkowska 1',
city: 'Warszawa',
postcode: '00-001',
country: 'Polska'
);
echo $address->format(); // ul. Marszałkowska 1, 00-001 Warszawa, Polska
3. Nowe funkcje tablicowe – array_first() i array_last()
<?php
declare(strict_types=1);
// PHP 8.5 - array_first() i array_last()
// Uzupełnienie array_find() z PHP 8.4
$products = [
['id' => 1, 'name' => 'Widget', 'price' => 29.99],
['id' => 2, 'name' => 'Gadget', 'price' => 49.99],
['id' => 3, 'name' => 'Doohickey', 'price' => 9.99],
];
// array_first - pierwszy element (bez warunku)
// Zamiast: reset($array) lub $array[array_key_first($array)]
$first = array_first($products);
// ['id' => 1, 'name' => 'Widget', 'price' => 29.99]
// array_last - ostatni element
// Zamiast: end($array) lub $array[array_key_last($array)]
$last = array_last($products);
// ['id' => 3, 'name' => 'Doohickey', 'price' => 9.99]
// Zachowanie dla pustej tablicy
$empty = [];
var_dump(array_first($empty)); // NULL
var_dump(array_last($empty)); // NULL
// W połączeniu z array_find() z PHP 8.4 - kompletny zestaw
$cheapest = array_find($products, fn($p) => $p['price'] < 15);
$firstItem = array_first($products);
$lastChanged = array_last($sortedByDate);
W głosowaniu – wynik niepewny
Deprecation: implicit nullable bez ? – PHP 8.5
<?php
// RFC proponuje deprecated w 8.5, Error w 8.6/9.0
// PHP 8.4 dodał E_DEPRECATED warning
// PHP 8.5 RFC: zamienić na E_DEPRECATED + notice w dokumentacji
// Przed - działa do PHP 8.3, deprecated od 8.4
function foo(string $bar = null): void {}
// Poprawnie - zawsze tak pisz
function foo(?string $bar = null): void {}
// Jeśli RFC przejdzie - kolejne narzędzia automatycznej naprawy
// Rector już obsługuje tę transformację:
// vendor/bin/rector process src --only=\Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallRector
Co nie przeszło lub nie przejdzie
Generics – brak RFC który zebrałby konsensus techniczny. Różnica między reified (Java-style, wymaga zmian w engine) a erased (TypeScript-style, tylko compile time) nadal dzieli społeczność. PHPStan templates pozostają de facto standardem.
Async/await natywnie – Fibers z PHP 8.1 to niskopoziomowy building block, ale high-level async/await API nie ma RFC. ReactPHP i Amp 3.x robią to przez biblioteki.
Pattern matching – match() z PHP 8.0 obsługuje podstawowe przypadki. Pełny pattern matching (dekonstrukcja, guards) to prawdopodobnie PHP 9.x.
Kiedy PHP 8.5?
# Harmonogram PHP (typowy)
# Styczeń-czerwiec: RFC dyskusje i głosowania
# Lipiec: Feature Freeze - żadne nowe RFC
# Sierpień-wrzesień: Alpha, Beta
# Październik-październik: RC1, RC2, RC3
# Listopad: Final release
# PHP 8.5 spodziewane: listopad 2025
# Testowanie RC1 gdy wyjdzie:
docker pull php:8.5-rc-cli
docker run --rm php:8.5-rc-cli php -v
# Sprawdź czy Twój kod jest kompatybilny z RC
docker run --rm -v $(pwd):/app php:8.5-rc-cli \
php /app/vendor/bin/phpstan analyse --level=8 /app/src
Magento 2 i PHP 8.5
Jeśli historia się powtarza: PHP 8.5 (listopad 2025) → Magento 2.4.9 z oficjalnym wsparciem (~Q2 2026). Własny kod modułów można pisać pod PHP 8.5 wcześniej, ale zalecam trzymanie się 8.4 dla Magento projektów do momentu oficjalnego wsparcia.
Pipe operator będzie szczególnie przydatny przy przetwarzaniu kolekcji Magento:
<?php
// PHP 8.5 pipe operator w kontekście Magento 2
$activeProductSkus = $this->productRepository
->getList($searchCriteria)
->getItems()
|> array_filter(..., fn($p) => $p->getStatus() === 1)
|> array_map(..., fn($p) => $p->getSku())
|> array_values(...)
;
// Zamiast dzisiaj:
$items = $this->productRepository->getList($searchCriteria)->getItems();
$active = array_filter($items, fn($p) => $p->getStatus() === 1);
$skus = array_map(fn($p) => $p->getSku(), $active);
$activeProductSkus = array_values($skus);
Podsumowanie
PHP 8.5 przynosi dwie zmiany które od razu wejdą do kodu: pipe operator eliminuje zagnieżdżone wywołania funkcji i zmienne tymczasowe, readonly class dziedziczenie domyka logikę wprowadzoną w PHP 8.2. Reszta to mniejsze uzupełnienia (array_first/last) i deprecacje przygotowujące do PHP 9.0. Śledzę php-internals przez Feature Freeze – aktualizacja pojawi się po RC1.
