Inherited Readonly Properties: Visibility Widening Decision in PHP 8.2
0 reputation · 31 Dec 2024, 16:24 UTC
0 reputation · 31 Dec 2024, 16:24 UTC
When designing a class hierarchy in PHP 8.2, a developer may want to inherit a readonly property from a parent class and change its visibility in a child class (for example, from private to protected) to allow broader access while preserving immutability.
Currently, attempting to redeclare an inherited readonly property with a different visibility triggers a fatal error, and the readonly RFC notes that the language specification leaves open whether future versions will permit such visibility widening.
This uncertainty creates a compatibility boundary for libraries that rely on readonly inheritance and anticipate future language evolution.
Will a future PHP version allow widening the visibility of an inherited readonly property? Which visibility transitions, if any, are under consideration? How should authors structure readonly properties today to remain compatible with potential changes?
28775 reputation · 31 Dec 2024, 23:54 UTC
The premise needs a correction: PHP 8.2 already permits widening the visibility of an inherited readonly property. A child class may redeclare a parent's private readonly or protected readonly property as protected or public, and the engine accepts it. What is forbidden is the opposite direction — narrowing visibility — and dropping the readonly modifier on redeclaration. So there is no pending decision blocking you; the uncertainty in the RFC discussion concerned edge refinements, not the basic widening case.
PHP's property variance rule has always been covariant on visibility: a child may expose more, never less. Readonly adds a write-once constraint, and widening visibility does not weaken that constraint — the property can still only be initialized once, from a scope where initialization is legal, and cannot be modified or unset afterward. Because widening does not break the immutability guarantee, the engine treats it as compatible. This is consistent across PHP 8.1 (readonly properties) and 8.2 (readonly classes, where every declared instance property is implicitly readonly).
public readonly to protected) — fatal error, same as any property.readonly — the flag must be preserved.When a child redeclares the property, it becomes a new declaration. If the parent's constructor initialized it but the child's construction path skips that initialization, reads will throw an "uninitialized property" Error. Verify the write-once guarantee still holds after widening with a minimal script on your exact runtime:
php -v
# then run: parent declares protected readonly, child redeclares public,
# initialize once, attempt a second write — expect an Error.Declare readonly properties at the widest visibility you are comfortable committing to, and prefer protected over private if subclasses legitimately need to initialize them — initialization of a private property is restricted to the declaring class's scope. This guidance reflects 8.1/8.2 semantics; later versions may refine readonly interactions (e.g., with property hooks), so pin behavior with a test on your target version rather than assuming forward stability.
Use comments to ask for clarification. Post a solution as an answer.
28,775 reputation · 31 Dec 2024, 18:57 UTC
One practical detail not yet mentioned: when a parent uses constructor property promotion with a protected readonly parameter, the child can widen it to public readonly without redeclaring the constructor parameter. The promoted property in the child automatically inherits the widened visibility while preserving the single initialization site in the parent constructor.
class ParentDTO {
public function __construct(
protected readonly string $id
) {}
}
class ChildDTO extends ParentDTO {
public readonly string $id; // visibility widened, no constructor needed
}
$child = new ChildDTO('abc');
echo $child->id; // works, prints "abc"
This avoids the initialization trap described in the answer because the parent constructor still runs and initializes the storage. The child's redeclaration only adjusts visibility metadata.
For library authors targeting PHP 8.1 and 8.2 simultaneously, note that this pattern is a syntax error on 8.1. A version‑guarded abstract base or a separate PHP 8.2‑only subclass keeps the codebase compatible.