Readonly Classes (PHP 8.2)
debt(d3/e1/b3/t5)
Closest to 'default linter catches the common case' (d3); Rector and PHPStan (listed in detection_hints.tools) can flag classes where all properties are individually readonly and suggest the class-level keyword, and inheritance violations are fatal errors caught instantly by PHP itself.
Closest to 'one-line patch' (e1); per quick_fix, the change is adding the `readonly` keyword to the class declaration and removing it from each property — a trivial single-line modification per class.
Closest to 'localised tax' (b3); applies to individual value object / DTO classes, doesn't ripple across the system, though it does constrain inheritance hierarchies for those classes.
Closest to 'notable trap' (t5); per misconception, the inheritance rules (readonly class can only extend readonly or property-less abstract classes) are a documented gotcha, plus the confusion with PHP 8.1 readonly properties and the typed-property requirement listed in common_mistakes.
Also Known As
TL;DR
Explanation
PHP 8.2 introduced readonly classes: declaring readonly class Point {} makes every declared property automatically readonly and typed — no need to mark each individually. All properties must be typed; dynamic properties are forbidden. Readonly classes cannot be extended by non-readonly classes and cannot declare static properties. They are perfect for value objects, DTOs, and command/query objects where immutability should be a class-level guarantee. Combined with constructor promotion, they enable very concise immutable data structures with no setter methods to maintain.
Common Misconception
Why It Matters
Common Mistakes
- Confusing readonly classes (PHP 8.2, all properties readonly) with readonly properties (PHP 8.1, per-property).
- Attempting to declare non-typed properties in a readonly class — all properties must be typed.
- Using readonly classes for entities that need lazy loading or mutation after construction.
- Not realising that readonly classes can still have non-readonly static properties.
Code Examples
// PHP 8.1 — manual readonly on each property:
class Point {
public function __construct(
public readonly float $x,
public readonly float $y,
public readonly float $z,
) {}
}
// PHP 8.2 — class-level readonly:
readonly class Point {
public function __construct(
public float $x,
public float $y,
public float $z,
) {}
}
readonly class Coordinate {
public function __construct(
public float $lat,
public float $lng,
) {}
}