Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor
| From: | Rob Landers | Date: | Tue, 03 Feb 2026 09:30:30 +0000 |
| Subject: | Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor | ||
| References: | 1 2 3 4 5 6 7 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129991@lists.php.net to get a copy of this message | ||
On Tue, Feb 3, 2026, at 10:22, Nicolas Grekas wrote:
>
>
> Le mar. 3 févr. 2026 à 10:00, Rob Landers <rob@bottled.codes> a écrit :
>> __
>> On Tue, Feb 3, 2026, at 09:56, Nicolas Grekas wrote:
>>> Hi Rob,
>>>
>>> Le mar. 3 févr. 2026 à 09:50, Rob Landers <rob@bottled.codes> a écrit :
>>>> __
>>>> On Mon, Feb 2, 2026, at 22:14, Nicolas Grekas wrote:
>>>>> Hi Marco,
>>>>>
>>>>> Le lun. 2 févr. 2026 à 11:54, Marco Pivetta <ocramius@gmail.com> a
>>>>> écrit :
>>>>>> Hey Nicolas,
>>>>>>
>>>>>>
>>>>>> On Thu, 22 Jan 2026 at 16:34, Nicolas Grekas
>>>>>> <nicolas.grekas+php@gmail.com <mailto:nicolas.grekas%2Bphp@gmail.com{4Ý
>>>>>> ¹�³ >¯õ`Æ8¶>> wrote:
>>>>>>> Dear all,
>>>>>>>
>>>>>>> Here is a new RFC for you to consider:
>>>>>>>
>>>>>>> https://wiki.php.net/rfc/promoted_readonly_constructor_reassign
>>>>>>
>>>>>>
>>>>>> What happens if one calls
$obj->__construct(1, 2,
>>>>>> 3) (on an already instantiated $obj) in the context
>>>>>> of this patch?
>>>>>
>>>>> Thanks for asking, I didn't think about this. This made me also think
>>>>> about ReflectionClass::newInstanceWithoutConstructor().
>>>>> I clarified this in the RFC, see "Direct __construct() Calls Cannot Bypass
>>>>> Readonly" and "Reflection: Objects Created Without Constructor".
>>>>> Patch and PR updated also if anyone wants to run some code where this RFC can
>>>>> be played with.
>>>>>
>>>>> Cheers,
>>>>> Nicolas
>>>>
>>>> Hi Nicolas,
>>>>
>>>> Under "Child Classes Can Reassign Parent Properties": this feels like a
>>>> major footgun. Calling parent::__construct() won't allow a reset (per the rules of calling a
>>>> constructor directly); which would completely break inheritance... but then in the examples it says
>>>> that calling a constructor directly can reset it -- but you can't?
>>>>
>>>> This feels really inconsistent to me.
>>>>
>>>> — Rob
>>>
>>> Yes, the text was ambiguous. The implementation allows parent::__construct() during the
>>> initial construction (normal inheritance), and only blocks explicit __construct() calls after
>>> construction completed. I’ve clarified this in the RFC.
>>>
>>> Nicolas
>>
>> Will this result in a catchable error? I assume so, so a valid pattern during inheritance
>> might be to put these in a try/catch so children can set them first?
>>
>> FWIW, in my Records RFC, properties were fully mutable during construction for exactly this
>> reason.
>>
>>
>
> The existing behavior is preserved: if a reassignment fails, it throws a catchable Error. The
> implicit CPP assignment in a parent constructor happens before the parent body, so a child cannot
> "set first" and then call ''parent::__construct()'' to avoid it; a
> try/catch in the parent cannot intercept it. But a try/catch in the child can catch of course.
>
> Does that answer your question?
So, this could end up with partial application of state? Or does it rollback? For example:
class Box {
public function __construct(readonly int $x, readonly int $y, readonly bool $isSquare = false) {
$this->isSquare = $x == $y;
}
}
class Square extends Box {
public function __construct(readonly int $size) {
$this->isSquare = true;
try {
parent::__construct($size, $size); // what is the state after it throws?
} catch(\Throwable) {}
}
}
(I spent over a year thinking about this stuff ... so if you're interested in more edge cases,
I can dig up my notes)
— Rob