PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

PHP 8.2 – readonly classes in practice, deprecations, Rector, migration checklist

by Henryk Tews / Tuesday, 13 December 2022 / Published in PHP

PHP 8.2 was officially released on 8 December 2022. A month of using readonly classes in new modules and patching dynamic properties in older code gives a clear practical picture. I show what readonly classes look like in real Magento 2 code, the most common dynamic property violations and how to fix them with Rector, and a migration checklist for upgrading existing projects.

Readonly classes in production code – one month later

<?php

declare(strict_types=1);

// Before PHP 8.2 - manual readonly on each property
final class OrderCreatedEvent
{
    public function __construct(
        public readonly int $orderId,
        public readonly int $customerId,
        public readonly float $grandTotal,
        public readonly string $currency,
        public readonly \DateTimeImmutable $createdAt,
    ) {}
}

// PHP 8.2 - readonly on the class, no repetition
final readonly class OrderCreatedEvent
{
    public function __construct(
        public int $orderId,
        public int $customerId,
        public float $grandTotal,
        public string $currency,
        public \DateTimeImmutable $createdAt,
    ) {}
}

// Where I now use readonly classes by default:
// - Domain events  (OrderCreated, ProductUpdated, ...)
// - Command objects for CommandBus
// - Value Objects (Money, Address, ProductId, ...)
// - DTO classes for API responses
// - Configuration objects

Dynamic properties – common patterns and fixes

<?php

// PATTERN 1: DataObject subclasses - Magento's own classes use getData/setData
// These use magic methods (__get/__set) which ARE fine - not dynamic properties
class MyBlock extends \Magento\Framework\DataObject
{
    // Fine - DataObject handles this through internal $this->_data array
    public function getProduct(): mixed
    {
        return $this->getData('product'); // not a dynamic property
    }
}

// PATTERN 2: Custom data containers - actually setting dynamic properties
class OrderData
{
    // PHP 8.2 Warning: Creation of dynamic property
    public function __construct(array $data)
    {
        foreach ($data as $key => $value) {
            $this->$key = $value; // Dynamic property!
        }
    }
}

// Fix: use an internal array
class OrderData
{
    private array $data = [];

    public function __construct(array $data) { $this->data = $data; }
    public function __get(string $name): mixed { return $this->data[$name] ?? null; }
    public function __set(string $name, mixed $value): void { $this->data[$name] = $value; }
    public function __isset(string $name): bool { return isset($this->data[$name]); }
}

// PATTERN 3: Test classes - tests often assign dynamic properties to mock setup
// Fix: add #[AllowDynamicProperties] to the test class
#[\AllowDynamicProperties]
class MyTest extends \PHPUnit\Framework\TestCase
{
    // dynamic property assignments in setUp() are OK here
}

Rector for automatic migration

# Find all dynamic property violations
vendor/bin/phpstan analyse app/code/ --level=2 --php-version=8.2 \
    --error-format=table 2>&1 | grep "dynamic"

# Rector: automatically add #[AllowDynamicProperties] where needed
vendor/bin/rector process app/code/ \
    --only=\Rector\Php82\Rector\Class_\AllowDynamicPropertiesAttributeRector \
    --dry-run

# Rector: convert readonly class candidates
vendor/bin/rector process app/code/ \
    --only=\Rector\Php82\Rector\Class_\ReadOnlyClassRector \
    --dry-run

# Full PHP 8.2 ruleset
vendor/bin/rector process app/code/ --php-version=8.2 --dry-run

PHP 8.2 migration checklist

Item Command Priority
Find dynamic properties PHPStan level 2 High
Fix dynamic properties Rector AllowDynamicPropertiesAttributeRector High
Check deprecated utf8_encode/decode grep -r “utf8_encode\|utf8_decode” src/ Medium
Remove ${var} string interpolation PHPStan / IDE inspection Medium
Test Composer dependencies composer update –dry-run High
Run full test suite vendor/bin/phpunit High
# Deprecated in PHP 8.2 - replace with mb_ equivalents
grep -rn "utf8_encode\|utf8_decode" app/code/
# utf8_encode() -> mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1')
# utf8_decode() -> mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8')

# ${var} string interpolation deprecated - use {$var}
grep -rn '\${\w\+}' app/code/ | grep -v '//'
# "Hello ${name}" -> "Hello {$name}"

Summary

PHP 8.2 is a smooth upgrade compared to the 7.x to 8.0 jump. The main work is finding and fixing dynamic properties – Rector handles most of them automatically. Readonly classes are immediately useful for DTOs and value objects. For Magento 2 projects: test thoroughly on staging, check all third-party modules for PHP 8.2 compatibility, and pay attention to dynamic properties in model classes that use DataObject patterns.

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}