readonly Properties (PHP 8.1)
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The term's detection_hints lists rector and phpstan as tools, both specialist static analysis tools. The code_pattern suggests detecting old-style private property + getter patterns requires a tool like Rector or PHPStan to flag; it won't be caught by a compiler error or default linter, but these specialist tools can identify missing readonly opportunities or misuse (e.g. setting after construction throws a runtime Error, not a compile-time one in most cases).
Closest to 'simple parameterised fix' (e3). The quick_fix states 'Add readonly to immutable properties. Combine with constructor promotion for concise value objects. Use readonly class (PHP 8.2) for fully immutable types.' This is a small, mechanical refactor — replacing private property + getter patterns with readonly — confined to one class at a time, not a cross-cutting architectural change.
Closest to 'localised tax' (b3). readonly is a property-level modifier. Its applies_to spans web, cli, and queue-worker contexts broadly, but the structural commitment is per-class — once applied, a readonly property is immutable by design. The burden is that value objects using readonly need a with() method pattern (pre-8.3) or __clone() (8.3+) for modifications, but this is localised to the class itself rather than spreading across the codebase.
Closest to 'notable trap' (t5). The misconception field explicitly states: 'readonly prevents cloning with modifications — PHP 8.3 allows modifying readonly properties in __clone(). Before 8.3, use a with() method that returns a new instance.' This is a documented gotcha that most developers encounter when trying to clone or mutate readonly value objects, and the version-dependent behaviour (pre-8.3 vs 8.3+) adds another layer of surprise. The common_mistakes also include the type declaration requirement and missing readonly class syntax.
TL;DR
Explanation
public readonly string $name; cannot be written after initialization. Writing a second time throws Error. Cannot be unset. Must be typed. Cannot have a default value in declaration (except via constructor promotion). Combined with constructor promotion: public function __construct(public readonly string $name) — declare + promote + make readonly in one line. PHP 8.2 readonly classes: mark the entire class and all promoted properties are readonly. PHP 8.3 allows readonly property modification in __clone() for clone-with patterns. Use readonly for value objects, DTOs, request objects.
Common Misconception
Why It Matters
Common Mistakes
- Trying to set readonly property after construction — throws Error.
- Forgetting readonly requires a type declaration.
- Not using readonly class (8.2) when all properties should be immutable.
Code Examples
class Money {
private int $amount;
private string $currency;
public function getAmount(): int { return $this->amount; }
public function getCurrency(): string { return $this->currency; }
}
// PHP 8.1 — readonly + constructor promotion:
class Money {
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {}
}
// PHP 8.2 — readonly class:
readonly class Money {
public function __construct(
public int $amount,
public string $currency,
) {}
}