Hi
On 2/16/26 19:20, Nicolas Grekas wrote:
To be sure I understood you well: you are suggesting that mutability should
be scoped to the constructor that declares the property (not any
constructor on the object).
Yes, because otherwise you might rely on implementation details of the
parent constructor: Depending on whether the parent constructor
reassigns internally, your reassignment in the child constructor either
succeeds or fails.
This makes sense and I’ve implemented exactly that model:
- Reassignment is allowed only while the declaring class constructor is
active (including methods/closures called from it).
- A child constructor can no longer reassign a parent-declared promoted
readonly property.
- “Child sets first, then parent::__construct()” now throws as expected.
- The thrown Error is catchable from the child (around
parent::__construct()), but not from inside the parent body before implicit
CPP init.
- Calling __construct() on an already-constructed object still cannot
mutate readonly state.
I also updated the RFC text and examples to state this explicitly, and
added/updated tests for the inheritance/preemption scenarios.
Thank you. I've checked the RFC and the explanation and semantics make
sense to me. I've also reviewed (parts) of the tests and provided some
feedback there. I'll take another look at the tests when you made the
adjustments to make sure that everything in the RFC is properly tested
to make sure we didn't miss and edge case.
Anything else?
Yes, there is one edge case related to inheritance that isn't mentioned
in the RFC and from what I see it's not tested either.
Child classes can redefine readonly properties and they are then “owned”
by the child class. Thus we need to explain what happens in that case.
I've prepared example for the three relevant cases I can think of. The
follow from the existing semantics in a straight-forward fashion, but
it's good to spell them out explicitly (and particularly test them).
1. Parent uses CPP, child redefines and reassigns.
class P1 {
public function __construct(
public readonly string $x = 'P',
) { }
}
class C1 extends P1 {
public readonly string $x;
public function __construct() {
parent::__construct();
$this->x = 'C'; // This should fail.
}
}
2. Parent uses CPP and reassigns, child redefines.
class P2 {
public function __construct(
public readonly string $x = 'P1',
) {
$this->x = 'P2'; // This should be legal.
}
}
class C2 extends P2 {
public readonly string $x;
public function __construct() {
parent::__construct();
}
}
3. Parent uses CPP, child uses CPP redefinition.
class P3 {
public function __construct(
public readonly string $x = 'P',
) { }
}
class C3 extends P3 {
public function __construct(
public readonly string $x = 'C1',
) {
parent::__construct(); // This should fail.
}
}
Best regards
Tim Düsterhus