Abstract readonly Properties
debt(d1/e1/b3/t3)
Closest to 'caught instantly (compiler/syntax error)' (d1). Using abstract readonly in PHP < 8.4 is a syntax error caught immediately by the runtime. Implementing with a non-readonly property is a type/compile-time error. PHPStan and Psalm (listed tools) can also catch misuse statically, but the primary detection is the PHP engine itself at parse/compile time.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix states: declare 'abstract readonly Type $property' in abstract classes. Correcting a wrong implementation (non-readonly property or wrong PHP version) is a single-line declaration change per property, grounding directly in the quick_fix guidance.
Closest to 'localised tax (one component pays)' (b3). The concept applies to abstract classes using PHP 8.4+ features — its reach is limited to classes opting into this pattern. It imposes a PHP version floor (8.4) on those classes and their implementors, but does not propagate a burden across the whole codebase. The applies_to scope (web, cli, queue-worker) is broad in principle, but only classes using the keyword are affected.
Closest to 'minor surprise (one edge case)' (t3). The misconception field states developers may believe this existed before PHP 8.4, when it was only introduced in 8.4. There is also a common mistake of confusing with readonly classes (PHP 8.2). These are versioning and concept-boundary surprises, but they are relatively contained and documented; a competent developer will discover them quickly via syntax errors or documentation.
TL;DR
Explanation
PHP 8.4 introduced abstract readonly properties. An abstract class or interface can declare abstract public readonly string $name; requiring any concrete implementation to provide a readonly property with that name and type. This is more expressive than an abstract getter method — it declares structural intent. Implementations must declare the property as readonly (not just any property). This enables value-object hierarchies and interface-driven readonly DTOs. Before PHP 8.4, this required abstract getter methods as a workaround.
Common Misconception
Why It Matters
Common Mistakes
- Trying to use abstract readonly in PHP < 8.4 — syntax error.
- Implementing with a non-readonly property — type error.
- Confusing with readonly classes (PHP 8.2) which make all properties readonly.
Code Examples
// PHP 8.3 workaround — method instead of property:
abstract class Entity {
abstract public function getId(): int;
}
// PHP 8.4:
abstract class Entity {
abstract public readonly int $id;
}
class User extends Entity {
public function __construct(
public readonly int $id,
public readonly string $name,
) {}
}