PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

PHP 8.2 po premierze – readonly classes w praktyce, deprecacje, Rector, checklist migracji

by Henryk Tews / wtorek, 13 grudnia 2022 / Opublikowano w PHP

PHP 8.2 wyszło oficjalnie 8 grudnia 2022. W lipcu pisałem o zapowiedziach – teraz, gdy mam finalną wersję w rękach, czas na uczciwe podsumowanie. Co faktycznie weszło do specyfikacji, co zostało odłożone i jakie są pierwsze praktyczne wrażenia z migracji projektu PHP na nową wersję.

Co weszło do PHP 8.2

Readonly Classes – działa dokładnie jak zapowiadano

<?php

declare(strict_types=1);

// Readonly class - w produkcji od 8 grudnia 2022
readonly class Address
{
    public function __construct(
        public string $street,
        public string $city,
        public string $postcode,
        public string $country = 'PL'
    ) {}

    public function format(): string
    {
        return "{$this->street}, {$this->postcode} {$this->city}, {$this->country}";
    }

    // Niemutowalne - tworzysz nowy obiekt zamiast modyfikować
    public function withCity(string $city): self
    {
        return new self($this->street, $city, $this->postcode, $this->country);
    }
}

readonly class OrderSnapshot
{
    public function __construct(
        public int $orderId,
        public string $status,
        public float $grandTotal,
        public Address $shippingAddress,
        public \DateTimeImmutable $createdAt
    ) {}
}

// Readonly classes zagnieżdżone w sobie działają poprawnie
$address = new Address('ul. Marszałkowska 1', 'Warszawa', '00-001');
$order   = new OrderSnapshot(
    orderId:         1042,
    status:          'processing',
    grandTotal:      299.99,
    shippingAddress: $address,
    createdAt:       new \DateTimeImmutable()
);

echo $order->shippingAddress->format();
// ul. Marszałkowska 1, 00-001 Warszawa, PL

Readonly Classes – niespodziewane ograniczenie: unserialize()

Przy wdrożeniu odkryłem jeden nieoczywisty problem: readonly classes nie mogą być deserializowane przez standardowe unserialize(), bo deserializacja próbuje ustawić właściwości po konstruktorze:

<?php

readonly class Config
{
    public function __construct(
        public string $apiKey,
        public string $endpoint
    ) {}
}

$config     = new Config('key-123', 'https://api.example.com');
$serialized = serialize($config);
$restored   = unserialize($serialized);
// Error: Cannot modify readonly property Config::$apiKey

// Rozwiązanie: implementuj __serialize() i __unserialize()
readonly class ConfigSerializable
{
    public function __construct(
        public string $apiKey,
        public string $endpoint
    ) {}

    public function __serialize(): array
    {
        return ['apiKey' => $this->apiKey, 'endpoint' => $this->endpoint];
    }

    public function __unserialize(array $data): void
    {
        // __unserialize wywołuje się przed __construct - readonly można tu ustawić
        // To jedyne miejsce gdzie PHP pozwala na inicjalizację readonly po new
        $this->apiKey   = $data['apiKey'];
        $this->endpoint = $data['endpoint'];
    }
}

Nowe typy: null, false i true jako samodzielne typy

<?php

declare(strict_types=1);

// PHP 8.2 - null, false i true jako pełnoprawne typy zwracane
// (false i true były już w union types, teraz mogą stać samodzielnie)

// Przed PHP 8.2 - false w union
function findUser(int $id): User|false { /* ... */ }

// PHP 8.2 - false jako samodzielny typ (rzadko przydatne)
function isLegacyMode(): false|true { /* ... */ }

// true jako typ - funkcja która zawsze zwraca true lub rzuca wyjątek
function assertPositive(int $n): true
{
    if ($n <= 0) {
        throw new \InvalidArgumentException("Expected positive number, got {$n}");
    }
    return true;
}

// null jako samodzielny typ zwracany - funkcja nic nie zwraca
// (równoważne void, ale można użyć w union)
function logMessage(string $msg): null
{
    file_put_contents('/var/log/app.log', $msg . "\n", FILE_APPEND);
    return null; // lub po prostu return;
}

Nowe funkcje w bibliotece standardowej

<?php

// ini_parse_quantity() - parsowanie wartości z php.ini
$bytes = ini_parse_quantity('256M'); // 268435456
$bytes = ini_parse_quantity('2G');   // 2147483648
$bytes = ini_parse_quantity('512K'); // 524288

// Przydatne gdy sprawdzasz limity w kodzie diagnostycznym
$memoryLimit = ini_parse_quantity(ini_get('memory_limit'));
$uploadLimit = ini_parse_quantity(ini_get('upload_max_filesize'));

if ($memoryLimit < 512 * 1024 * 1024) {
    echo "Ostrzeżenie: memory_limit poniżej 512M\n";
}

// curl_upkeep() - utrzymanie idle connections w curl_multi
// odFetch open connections bez nowych requestów

// mysqli_execute_query() - skrót dla prepared statement
$users = mysqli_execute_query($link, 'SELECT * FROM users WHERE status = ?', ['active']);
// Zamiast: prepare -> bind_param -> execute -> get_result

Co zostało odłożone lub nie weszło

Kilka RFC które były dyskutowane nie trafiło do PHP 8.2:

  • Pipe operator (|>) - odłożony, prawdopodobnie PHP 8.3 lub 9.0
  • Partial function application - wciąż w dyskusji
  • readonly dla właściwości statycznych - readonly class nadal nie obsługuje statycznych właściwości

Deprecacje które mają realne znaczenie dla Magento

<?php

// 1. Dynamiczne właściwości - najważniejsza deprecacja
class DataObject // uproszczony analog Magento\Framework\DataObject
{
    protected array $_data = [];

    public function __get(string $name): mixed
    {
        return $this->_data[$name] ?? null;
    }

    public function __set(string $name, mixed $value): void
    {
        $this->_data[$name] = $value;
    }
}

// DataObject i jego potomkowie w Magento MAJĄ __get/__set - ok
// Problem jest przy klasach które nie mają tych magicznych metod

class SimplePHPClass
{
    public string $name = '';
}

$obj = new SimplePHPClass();
$obj->customField = 'value'; // PHP 8.2: Deprecated warning
                              // PHP 9.0: Fatal Error

// Magento 2.4.6+ dodało #[AllowDynamicProperties] tam gdzie potrzeba

// 2. Callables w nowym stylu - kilka form deprecated
$fn = 'strlen';
strlen('test'); // ok
$fn('test');    // ok - callable string działa

// Deprecated: ${var} interpolacja w stringach (użyj {$var})
$key = 'name';
echo "Hello ${key}"; // Deprecated
echo "Hello {$key}"; // ok

Migracja projektu na PHP 8.2 – checklist

# 1. PHPStan z levelem 8 i target PHP 8.2
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level=8 --php-version=8.2

# 2. PHP Compatibility Checker
composer require --dev phpcompatibility/php-compatibility
vendor/bin/phpcs --standard=PHPCompatibility \
    --runtime-set testVersion 8.2 \
    src/

# 3. Rector - automatyczna migracja kodu
composer require --dev rector/rector
vendor/bin/rector process src --dry-run

# 4. Testy na PHP 8.2 w CI (GitHub Actions)
# strategy:
#   matrix:
#     php: ['8.1', '8.2']

Rector – automatyczna migracja

<?php

// rector.php - konfiguracja automatycznej migracji
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
    ->withSets([
        LevelSetList::UP_TO_PHP_82,
        SetList::CODE_QUALITY,
        SetList::DEAD_CODE,
    ])
    ->withPhpSets(php82: true);

// Rector automatycznie:
// - konwertuje ${var} na {$var} w stringach
// - dodaje return types tam gdzie może je wywnioskować
// - aktualizuje sygnatury metod zgodnie z PHP 8.2

Podsumowanie

PHP 8.2 to solidne wydanie – readonly classes od razu wchodzą do wszystkich nowych DTO i value objects, co znacznie redukuje boilerplate. Deprecacja dynamicznych właściwości to porządkowanie języka przed PHP 9.0 – warto przejrzeć kod pod kątem klas które polegają na tym mechaniźmie. Migracja z PHP 8.1 do 8.2 jest prosta jeśli projekt ma dobre pokrycie testami i PHPStan na poziomie 6+.

About Henryk Tews

Co możesz przeczytać następne

GraphQL Federation – subgrafy, gateway, Apollo Router, integracja z Magento 2
PHP 7.4 preview – typed properties, arrow functions, spread w tablicach, preloading
PHP 8.1 preview – enumy, readonly properties, intersection types, fibers
  • 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}