PHP 8.2 arrives in November 2022 and its headline features are more conservative than 8.0 or 8.1, but each one plugs a real gap in the type system. Readonly classes eliminate boilerplate in immutable DTOs. DNF types allow complex type expressions that were impossible before. Deprecating dynamic properties starts enforcing something good OOP practice demanded for years.
Readonly classes – immutable DTOs without effort
<?php
declare(strict_types=1);
// PHP 8.1 - had to mark each property individually
class ProductDto
{
public function __construct(
public readonly int $id,
public readonly string $sku,
public readonly string $name,
public readonly float $price,
public readonly bool $isActive,
) {}
}
// PHP 8.2 - readonly on the class makes ALL properties readonly
readonly class ProductDto
{
public function __construct(
public int $id,
public string $sku,
public string $name,
public float $price,
public bool $isActive,
) {}
}
// Modification attempt throws Error
$dto = new ProductDto(42, 'SKU-001', 'Test', 99.99, true);
// $dto->price = 79.99; // Error: Cannot modify readonly property
// Create a modified copy using with-syntax (PHP 8.2)
$discounted = new ProductDto(
id: $dto->id,
sku: $dto->sku,
name: $dto->name,
price: $dto->price * 0.9,
isActive: $dto->isActive,
);
Readonly classes with inheritance
<?php
declare(strict_types=1);
readonly class Money
{
public function __construct(
public int $amount,
public string $currency,
) {}
public function add(self $other): static
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException('Currency mismatch');
}
return new static($this->amount + $other->amount, $this->currency);
}
}
// PHP 8.2: readonly class can be extended by another readonly class
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));
}
}
$price = new TaxedMoney(12300, 'PLN', 0.23); // 123.00 PLN gross
echo $price->netAmount(); // 10000 (100.00 PLN net)
DNF Types – Disjunctive Normal Form
<?php
interface Stringable { public function __toString(): string; }
interface Countable { public function count(): int; }
interface Serialisable { public function serialise(): string; }
// PHP 8.1 could do intersection: A&B
// PHP 8.1 could do union: A|B
// PHP 8.2 can combine both: (A&B)|C - DNF type
// Accepts: something that is BOTH Countable AND Stringable, OR a plain string
function processCollection((Countable&Stringable)|string $data): string
{
if (is_string($data)) {
return $data;
}
return "Collection with {$data->count()} items: {$data}";
}
// Accept null OR (Countable AND Stringable)
function maybeProcess((Countable&Stringable)|null $data): string
{
if ($data === null) return 'empty';
return "Count: {$data->count()}";
}
Dynamic properties deprecated
<?php
class Product
{
public string $name = '';
public float $price = 0.0;
}
$product = new Product();
$product->name = 'Widget'; // OK - declared property
$product->color = 'red'; // PHP 8.2: Deprecated: Creation of dynamic property
// PHP 9.0: Fatal Error
// Fix 1: Declare the property
class Product
{
public string $name = '';
public float $price = 0.0;
public string $color = ''; // declare it
}
// Fix 2: Allow dynamic properties explicitly (for legacy code)
#[\AllowDynamicProperties]
class LegacyProduct
{
public string $name = '';
}
$product = new LegacyProduct();
$product->anything = 'value'; // OK with the attribute
Impact on Magento 2 – what to check
# Find dynamic property usage in your modules
grep -rn "\$this->" app/code/ --include="*.php" | \
grep -v "public\|protected\|private" | \
grep -v "//" | head -50
# PHPStan catches this at level 2+
vendor/bin/phpstan analyse app/code/ --level=2 --php-version=8.2
# Rector can add #[AllowDynamicProperties] automatically to classes that need it
vendor/bin/rector process app/code/ \
--only=\Rector\Php82\Rector\Class_\AllowDynamicPropertiesAttributeRector \
--dry-run
Summary
PHP 8.2 is an evolutionary release that tightens the screws. Readonly classes make immutable DTOs the zero-cost default. DNF types complete the type algebra. Dynamic property deprecation enforces 20 years of OOP best practice at the language level. For Magento 2 projects the dynamic properties deprecation is the most pressing item – run PHPStan and Rector now to find and fix the affected classes before upgrading.
