[RFC] Allow Reassignment of Promoted Readonly Properties in Constructor

php.internals

Nicolas Grekas

222 days ago
Dear all, Here is a new RFC for you to consider: https://wiki.php.net/rfc/promoted_readonly_constructor_reassign As a quick intro, my motivation for that RFC is that I find it quite annoying that readonly properties play badly with CPP (constructor property promotion). Doing simple processing of any argument before assigning it to a readonly property forces opting out of CPP. This RFC would allow setting once a readonly property in the body of a constructor after the property was previously (and implicitly) set using CPP. This allows keeping property declarations in their compact form while still enabling validation, normalization, or conditional initialization. Cheers, Nicolas

Derick Rethans

222 days ago
On Thu, 22 Jan 2026, Nicolas Grekas wrote:
> Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign
I read in the linked PR: "Team work with Claude Code opus 4.5 💪" This makes me instantly want to vote no to this. Code LLMs have been trained on all kinds of open source (and perhaps proprietary?) software. Open Source nearly always *atleast* has an "Attribution Required" license. But, these "tools" do not follow these licenses and the required. Therefore, there is no legally possible way to allow AI/LLM contributions into the PHP source code. cheers, Derick

Deleu

222 days ago
On Thu, Jan 22, 2026 at 1:02 PM Derick Rethans <derick@php.net> wrote:
> On Thu, 22 Jan 2026, Nicolas Grekas wrote: > > > Here is a new RFC for you to consider: > > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign > > I read in the linked PR: > > "Team work with Claude Code opus 4.5 💪" > > This makes me instantly want to vote no to this. >
I *assume* it was *not* your intention, but the stated message here then is: if you use an LLM to contribute to PHP, keep that information private / do not disclose that information, otherwise you run the risk of getting your contribution rejected due to personal subjective opinion beyond the merits of your contribution.
> Code LLMs have been trained on all kinds of open source (and perhaps > proprietary?) software. Open Source nearly always *atleast* has an > "Attribution Required" license. But, these "tools" do not follow these > licenses and the required. > > Therefore, there is no legally possible way to allow AI/LLM > contributions into the PHP source code. > > cheers, > Derick
I understand you have a point from the legal perspective, but as a thought experiment we can consider any human-produced contribution potentially tainted with lack of attribution under the same scrutiny, but it wouldn't ever matter. I definitely don't want to be debating in favor of AI use, the accelerated method of burning down the planet or even how OSS is being bombarded by extremely low quality AI-driven contributions. However, I don't think it's in the spirit of OSS, the RFC nor PHP's best interest to debate AI like that on a specific RFC from an author that is known for high quality OSS contributions. Make an RFC and ban any use of AI on the PHP project for all I care, but let's aim to be fair with the RFC being proposed as any other RFC while such a rule/guideline doesn't exist.
-- Marco Deleu

Tim Düsterhus

221 days ago
Hi Am 2026-01-22 16:54, schrieb Derick Rethans:
> I read in the linked PR: > > "Team work with Claude Code opus 4.5 💪" > > This makes me instantly want to vote no to this.
What is being voted on as part of an RFC is the “concept”, not the specific implementation. Having an implementation available is often helpful to evaluate the feasibility of a concept and to figure out edge cases, but it is possible and regularly happens that the implementation changes quite a bit as part of the code review of the implementation. In fact for my own RFCs, I often have a fairly sloppy “PoC” implementation that I spend the time to clean up if / when I'm reasonably confident that the RFC will pass to avoid doing needless work. Or in this specific instance if the use of AI-assistance in the code is considered a problem, it would be possible for someone who is more familiar with the engine than Nicolas to do a “clean room” implementation based on the semantics outlined in the RFC. The RFC text should be judged on its own merit and should stand on its own, such that any “sufficiently capable” developer would be able to create the implementation - incl. all possible edge cases - based on the specification in the RFC text alone. Best regards Tim Düsterhus

Larry Garfield

222 days ago
On Thu, Jan 22, 2026, at 9:33 AM, Nicolas Grekas wrote:
> Dear all, > > Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign > > As a quick intro, my motivation for that RFC is that I find it quite > annoying that readonly properties play badly with CPP (constructor > property promotion). > > Doing simple processing of any argument before assigning it to a > readonly property forces opting out of CPP. > > This RFC would allow setting once a readonly property in the body of a > constructor after the property was previously (and implicitly) set > using CPP. > > This allows keeping property declarations in their compact form while > still enabling validation, normalization, or conditional initialization. > > Cheers, > Nicolas
I can see the benefit of this, though I do have some concerns.
> The reassignment must occur directly in the constructor body of the declaring class (not via method calls, closures, or other indirect means)
This could be an issue. I understand the argument for it, but for objects designed to be both constructed directly and deserialized the constructor may not always be called. A common solution is to move the shared logic out to a private method, say validate(), and then call validate() from both __construct() and __unserialize() (or the equivalent for a particular serializer). That would no longer be possible with this limitation, thus requiring duplication of code.
> Child classes cannot reassign parent's promoted readonly properties
Why?
> All other readonly semantics remain unchanged (no modification outside constructor, no unsetting, etc.)
The "no modification outside constructor" *is not part of the language semantics*. It is a recommended practice invented by SA tools. I also share Derick's disdain for code produced by Grand Theft Autocomplete, and the legal risks therein. --Larry Garfield

Tim Düsterhus

222 days ago
Hi Am 2026-01-22 16:33, schrieb Nicolas Grekas:
> Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign
Thank you. I have taken a look and have the following notes (for now): 1. In the Problem Statement: “Option 2: Use default parameter expressions (limited):” The example seems incorrect to me, particularly the “// Cannot use $x in default expression for $x” comment doesn't make sense. Can you check you pasted in the correct snippet? 2. Within the proposal: “The reassignment must occur directly in the constructor body of the declaring class (not via method calls, closures, or other indirect means)” I believe this is inconsistent with `__clone()` where the readonly property remains unlocked until the end of `__clone()` (and is then locked). I'm seeing it is explained further below as “This restriction exists because the check verifies that the current executing function is the constructor of the declaring class. When a method or closure executes, the current function changes, even if it was called from the constructor”, which effectively means that you defined the semantics based on the implementation instead of the other way around, which I consider problematic from a language design PoV. I'm positive it is possible to find a better implementation here. 3. Within the “RFC Impact” section you removed the “Ecosystem” subsection from the template. I believe mentioning the ecosystem impact is relevant here. This change will likely require adjustments to static analysis tools and IDEs to not point out the now-valid assignment as an error. 4. As per the updated RFC policy. Please already include the (closed) voting doodle in the RFC so there is no ambiguity here. Don't forget to include the “Abstain” option. My suggested title would just be using the RFC title followed by a questionmark :-) And please add a link to the list archives to the References section (it's required per the policy and useful for future research). For your convenience, the correct link is this: https://news-web.php.net/php.internals/129851 Best regards Tim Düsterhus

Nicolas Grekas

222 days ago
Thanks Tim, Larry, Le jeu. 22 janv. 2026 à 17:21, Tim Düsterhus <tim@bastelstu.be> a écrit :
> Hi > > Am 2026-01-22 16:33, schrieb Nicolas Grekas: > > Here is a new RFC for you to consider: > > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign > > Thank you. I have taken a look and have the following notes (for now): > > 1. In the Problem Statement: “Option 2: Use default parameter > expressions (limited):” > > The example seems incorrect to me, particularly the “// Cannot use $x in > default expression for $x” comment doesn't make sense. Can you check you > pasted in the correct snippet? > > 2. Within the proposal: “The reassignment must occur directly in the > constructor body of the declaring class (not via method calls, closures, > or other indirect means)” > > I believe this is inconsistent with `__clone()` where the readonly > property remains unlocked until the end of `__clone()` (and is then > locked). > > I'm seeing it is explained further below as “This restriction exists > because the check verifies that the current executing function is the > constructor of the declaring class. When a method or closure executes, > the current function changes, even if it was called from the > constructor”, which effectively means that you defined the semantics > based on the implementation instead of the other way around, which I > consider problematic from a language design PoV. I'm positive it is > possible to find a better implementation here. > > 3. Within the “RFC Impact” section you removed the “Ecosystem” > subsection from the template. > > I believe mentioning the ecosystem impact is relevant here. This change > will likely require adjustments to static analysis tools and IDEs to not > point out the now-valid assignment as an error. > > 4. As per the updated RFC policy. > > Please already include the (closed) voting doodle in the RFC so there is > no ambiguity here. Don't forget to include the “Abstain” option. My > suggested title would just be using the RFC title followed by a > questionmark :-) > > And please add a link to the list archives to the References section > (it's required per the policy and useful for future research). For your > convenience, the correct link is this: > https://news-web.php.net/php.internals/129851 > >
RFC text updated to account for your comments Tim, good catch and thanks for the help around RFC processes. About the assignment rule, Larry and you have the same reaction, so let me take this as a good description of the most expected behavior by the community ;-) I made the more restricted rule because I thought I should start with the stricter rule, and I get that would also be surprising somehow, so let's relax that. PR (and RFC) updated with the new logic, similar to __clone (and as boring as it can be ;P ) Cheers, Nicolas

Larry Garfield

221 days ago
On Thu, Jan 22, 2026, at 11:26 AM, Nicolas Grekas wrote:
> Thanks Tim, Larry, > > Le jeu. 22 janv. 2026 à 17:21, Tim Düsterhus <tim@bastelstu.be> a écrit : >> Hi >> >> Am 2026-01-22 16:33, schrieb Nicolas Grekas: >> > Here is a new RFC for you to consider: >> > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign >> >> Thank you. I have taken a look and have the following notes (for now): >> >> 1. In the Problem Statement: “Option 2: Use default parameter >> expressions (limited):” >> >> The example seems incorrect to me, particularly the “// Cannot use $x in >> default expression for $x” comment doesn't make sense. Can you check you >> pasted in the correct snippet? >> >> 2. Within the proposal: “The reassignment must occur directly in the >> constructor body of the declaring class (not via method calls, closures, >> or other indirect means)” >> >> I believe this is inconsistent with `__clone()` where the readonly >> property remains unlocked until the end of `__clone()` (and is then >> locked). >> >> I'm seeing it is explained further below as “This restriction exists >> because the check verifies that the current executing function is the >> constructor of the declaring class. When a method or closure executes, >> the current function changes, even if it was called from the >> constructor”, which effectively means that you defined the semantics >> based on the implementation instead of the other way around, which I >> consider problematic from a language design PoV. I'm positive it is >> possible to find a better implementation here. >> >> 3. Within the “RFC Impact” section you removed the “Ecosystem” >> subsection from the template. >> >> I believe mentioning the ecosystem impact is relevant here. This change >> will likely require adjustments to static analysis tools and IDEs to not >> point out the now-valid assignment as an error. >> >> 4. As per the updated RFC policy. >> >> Please already include the (closed) voting doodle in the RFC so there is >> no ambiguity here. Don't forget to include the “Abstain” option. My >> suggested title would just be using the RFC title followed by a >> questionmark :-) >> >> And please add a link to the list archives to the References section >> (it's required per the policy and useful for future research). For your >> convenience, the correct link is this: >> https://news-web.php.net/php.internals/129851 >> > > > RFC text updated to account for your comments Tim, good catch and > thanks for the help around RFC processes. > > About the assignment rule, Larry and you have the same reaction, so let > me take this as a good description of the most expected behavior by the > community ;-) > I made the more restricted rule because I thought I should start with > the stricter rule, and I get that would also be surprising somehow, so > let's relax that. > > PR (and RFC) updated with the new logic, similar to __clone (and as > boring as it can be ;P ) > > Cheers, > Nicolas
Boring is good in this case. :-) I like the updates. I only have two remaining quibbles.
> All other readonly semantics remain unchanged (no modification outside constructor, no unsetting, etc.)
As previously stated, "no modification outside constructor" is not, and has never been, part of the language semantics of readonly. In fact, the RFC has been updated now such that updates outside of the constructor are allowed, provide the constructor is in the call stack. Please remove that clause, as it is incorrect. And the LLM question, which warrants a separate discussion, I wager, and is not an issue of the RFC text. --Larry Garfield

Nicolas Grekas

220 days ago
Le ven. 23 janv. 2026 à 16:59, Larry Garfield <larry@garfieldtech.com> a écrit :
> On Thu, Jan 22, 2026, at 11:26 AM, Nicolas Grekas wrote: > > Thanks Tim, Larry, > > > > Le jeu. 22 janv. 2026 à 17:21, Tim Düsterhus <tim@bastelstu.be> a écrit > : > >> Hi > >> > >> Am 2026-01-22 16:33, schrieb Nicolas Grekas: > >> > Here is a new RFC for you to consider: > >> > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign > >> > >> Thank you. I have taken a look and have the following notes (for now): > >> > >> 1. In the Problem Statement: “Option 2: Use default parameter > >> expressions (limited):” > >> > >> The example seems incorrect to me, particularly the “// Cannot use $x > in > >> default expression for $x” comment doesn't make sense. Can you check > you > >> pasted in the correct snippet? > >> > >> 2. Within the proposal: “The reassignment must occur directly in the > >> constructor body of the declaring class (not via method calls, > closures, > >> or other indirect means)” > >> > >> I believe this is inconsistent with `__clone()` where the readonly > >> property remains unlocked until the end of `__clone()` (and is then > >> locked). > >> > >> I'm seeing it is explained further below as “This restriction exists > >> because the check verifies that the current executing function is the > >> constructor of the declaring class. When a method or closure executes, > >> the current function changes, even if it was called from the > >> constructor”, which effectively means that you defined the semantics > >> based on the implementation instead of the other way around, which I > >> consider problematic from a language design PoV. I'm positive it is > >> possible to find a better implementation here. > >> > >> 3. Within the “RFC Impact” section you removed the “Ecosystem” > >> subsection from the template. > >> > >> I believe mentioning the ecosystem impact is relevant here. This change > >> will likely require adjustments to static analysis tools and IDEs to > not > >> point out the now-valid assignment as an error. > >> > >> 4. As per the updated RFC policy. > >> > >> Please already include the (closed) voting doodle in the RFC so there > is > >> no ambiguity here. Don't forget to include the “Abstain” option. My > >> suggested title would just be using the RFC title followed by a > >> questionmark :-) > >> > >> And please add a link to the list archives to the References section > >> (it's required per the policy and useful for future research). For your > >> convenience, the correct link is this: > >> https://news-web.php.net/php.internals/129851 > >> > > > > > > RFC text updated to account for your comments Tim, good catch and > > thanks for the help around RFC processes. > > > > About the assignment rule, Larry and you have the same reaction, so let > > me take this as a good description of the most expected behavior by the > > community ;-) > > I made the more restricted rule because I thought I should start with > > the stricter rule, and I get that would also be surprising somehow, so > > let's relax that. > > > > PR (and RFC) updated with the new logic, similar to __clone (and as > > boring as it can be ;P ) > > > > Cheers, > > Nicolas > > Boring is good in this case. :-) I like the updates. I only have two > remaining quibbles. > > > All other readonly semantics remain unchanged (no modification outside > constructor, no unsetting, etc.) > > As previously stated, "no modification outside constructor" is not, and > has never been, part of the language semantics of readonly. In fact, the > RFC has been updated now such that updates outside of the constructor are > allowed, provide the constructor is in the call stack. Please remove that > clause, as it is incorrect. >
How would you phrase this? To me, point 3 above makes this clear: "The reassignment must occur while a constructor of the object is on the call stack (methods and closures called from the constructor are allowed)" Then, point 7: "All other readonly semantics remain unchanged (no modification outside constructor, no unsetting, etc.)" has enough context to me to be clear. No?

Larry Garfield

219 days ago
On Sat, Jan 24, 2026, at 12:09 PM, Nicolas Grekas wrote:
>> Boring is good in this case. :-) I like the updates. I only have two remaining quibbles. >> >> > All other readonly semantics remain unchanged (no modification outside constructor, no unsetting, etc.) >> >> As previously stated, "no modification outside constructor" is not, and has never been, part of the language semantics of readonly. In fact, the RFC has been updated now such that updates outside of the constructor are allowed, provide the constructor is in the call stack. Please remove that clause, as it is incorrect. > > How would you phrase this? > To me, point 3 above makes this clear: "The reassignment must occur > while a constructor of the object is on the call stack (methods and > closures called from the constructor are allowed)" > Then, point 7: "All other readonly semantics remain unchanged (no > modification outside constructor, no unsetting, etc.)" has enough > context to me to be clear. > > No?
There's a very subtle caveat here. Readonly properties that are not yet initialized can be set from anywhere, not just the constructor. That is the language semantic, regardless of what the PHPStan/Psalm authors think. :-) In this particular case, because the property is being set in the constructor preamble, essentially, it cannot then be re-assigned... EXCEPT in the constructor due to this RFC. But that's a subtle, easy to miss caveat. Hence why I'd prefer wording that doesn't imply (to those that don't catch that subtlety) that the language doesn't allow readonly to be assigned outside of the constructor. This is a particular pet peeve of mine because I have very good use cases for assigning to a readonly property outside of the constructor, but always therefore have to disable that check in PHPStan as a result. Hence why I harp on it so much. :-) --Larry Garfield

Nicolas Grekas

218 days ago
Hi Larry, Le dim. 25 janv. 2026 à 19:53, Larry Garfield <larry@garfieldtech.com> a écrit :
> On Sat, Jan 24, 2026, at 12:09 PM, Nicolas Grekas wrote: > > >> Boring is good in this case. :-) I like the updates. I only have two > remaining quibbles. > >> > >> > All other readonly semantics remain unchanged (no modification > outside constructor, no unsetting, etc.) > >> > >> As previously stated, "no modification outside constructor" is not, and > has never been, part of the language semantics of readonly. In fact, the > RFC has been updated now such that updates outside of the constructor are > allowed, provide the constructor is in the call stack. Please remove that > clause, as it is incorrect. > > > > How would you phrase this? > > To me, point 3 above makes this clear: "The reassignment must occur > > while a constructor of the object is on the call stack (methods and > > closures called from the constructor are allowed)" > > Then, point 7: "All other readonly semantics remain unchanged (no > > modification outside constructor, no unsetting, etc.)" has enough > > context to me to be clear. > > > > No? > > There's a very subtle caveat here. Readonly properties that are not yet > initialized can be set from anywhere, not just the constructor. That is > the language semantic, regardless of what the PHPStan/Psalm authors think. > :-) > > In this particular case, because the property is being set in the > constructor preamble, essentially, it cannot then be re-assigned... EXCEPT > in the constructor due to this RFC. But that's a subtle, easy to miss > caveat. Hence why I'd prefer wording that doesn't imply (to those that > don't catch that subtlety) that the language doesn't allow readonly to be > assigned outside of the constructor. > > This is a particular pet peeve of mine because I have very good use cases > for assigning to a readonly property outside of the constructor, but always > therefore have to disable that check in PHPStan as a result. Hence why I > harp on it so much. :-)
I updated the text to make this more clear, thanks for the suggestion!

Volker Dusch

220 days ago
On Thu, Jan 22, 2026 at 4:39 PM Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> > Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign >
Thank you very much for the RFC. I really enjoyed reading it; clearly explained, easy to read, and with a good focus on laying out how everything works and considering edge cases. Especially the working examples around "Child Classes Can Reassign Parent Properties" have been great to read. It's delightful that you were able to provide a PoC implementation for me to check and answer the remaining questions I had. Great to see more people being able to modify php-src to this scope. I think this makes sense, aligns behavior with clone, and patches up one edge case with using CPP over regular properties, easing refactoring. While this is not a problem I, personally, ran into, it makes sense to me, and given any upcoming insights in the discussion, I'm in favor of it. Thank you, Volker

Claude Pache

216 days ago
> Le 22 janv. 2026 à 16:33, Nicolas Grekas <nicolas.grekas+php@gmail.com> a écrit : > > Dear all, > > Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign > > As a quick intro, my motivation for that RFC is that I find it quite annoying that readonly properties play badly with CPP (constructor property promotion). > > Doing simple processing of any argument before assigning it to a readonly property forces opting out of CPP. > > This RFC would allow setting once a readonly property in the body of a constructor after the property was previously (and implicitly) set using CPP. > > This allows keeping property declarations in their compact form while still enabling validation, normalization, or conditional initialization. > > Cheers, > Nicolas
Hi, I am reserved about the proposal, because this style of using CPP and processing the value after the fact tends to favour brevity at the expense of precision and clarity. Let’s illustrate that with two examples from the RFC. First: ```php class Config { public function __construct( public readonly ?string $cacheDir = null, ) { $this->cacheDir ??= sys_get_temp_dir() . '/app_cache'; } } ``` As of today you can write: ```php class Config { public readonly string $cacheDir; public function __construct( ?string $cacheDir = null, ) { $this->cacheDir = $cacheDir ??= sys_get_temp_dir() . '/app_cache'; } } ``` Note that the property is marked as non-nullable, a precision that may be useful for both programmers and static analysers. With your proposal, there is no way to keep this information. The second example is similar: ```php class User { public function __construct( public readonly string $email, ) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } $this->email = strtolower($email); // Normalize } } ``` As of today, it can be written as: ```php class User { /** @var non-empty-string & lowercase-string */ public readonly string $email; public function __construct( string $email, ) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } $this->email = strtolower($email); // Normalize } } ``` With your proposal, there is no obvious way to keep the additional information provided in the phpdoc. Maybe we could imagine something like that: ```php class User { /** * @param string $email the e-mail address as provided */ public function __construct( /** @var non-empty-string & lowercase-string the normalised e-mail address */ string $email, ) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } $this->email = strtolower($email); // Normalize } } ``` but it is obviously clearer (at least to my eyes) to keep the property and the constructor parameter separate. One additional thought: Readonly properties carry a constraint that is annoying at first, but that is finally beneficial for the clarity of code that is written. When initialising such a property with something more complex than what can be comfortably written in a single expression, I am forced to write the intermediate results in a temporary variable and to assign the final value to the property at the end of the process, instead of transforming gradually the value of the property. The resulting code is a few lines longer, but it is no less clear, even it is often clearer, because it is obvious that this specific assignment supplies the final value of the property, and there is no need to look further down to see whether the value will undergo some additional transformations. As of today, this “final assignment” may be part of the constructor signature; with this RFC implemented, one can no longer know at a glance whether this assignment is “final”. (Also I sympathise with Larry: rigid coding styles and static analysers’ promoted “good practices” add problematic limitations that are not part of the semantics of language. I prefer disabling checks in PHPStan rather than downgrading to non-safe mutable properties and/or writing getters around them.) —Claude

Nicolas Grekas

215 days ago
Hi Claude, Le mer. 28 janv. 2026 à 19:53, Claude Pache <claude.pache@gmail.com> a écrit :
> > > Le 22 janv. 2026 à 16:33, Nicolas Grekas <nicolas.grekas+php@gmail.com> a > écrit : > > Dear all, > > Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign > > As a quick intro, my motivation for that RFC is that I find it quite > annoying that readonly properties play badly with CPP (constructor property > promotion). > > Doing simple processing of any argument before assigning it to a readonly > property forces opting out of CPP. > > This RFC would allow setting once a readonly property in the body of a > constructor after the property was previously (and implicitly) set using > CPP. > > This allows keeping property declarations in their compact form while > still enabling validation, normalization, or conditional initialization. > > Cheers, > Nicolas > > > > Hi, > > I am reserved about the proposal, because this style of using CPP and > processing the value after the fact tends to favour brevity at the expense > of precision and clarity. Let’s illustrate that with two examples from the > RFC. First: > > ```php > class Config { > public function __construct( > public readonly ?string $cacheDir = null, > ) { > $this->cacheDir ??= sys_get_temp_dir() . '/app_cache'; > } > } > ``` > > As of today you can write: > > ```php > class Config { > public readonly string $cacheDir; > > public function __construct( > ?string $cacheDir = null, > ) { > $this->cacheDir = $cacheDir ??= sys_get_temp_dir() . '/app_cache'; > } > } > ``` > > Note that the property is marked as non-nullable, a precision that may be > useful for both programmers and static analysers. With your proposal, there > is no way to keep this information. > > > The second example is similar: > > ```php > class User { > public function __construct( > public readonly string $email, > ) { > if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { > throw new InvalidArgumentException('Invalid email'); > } > $this->email = strtolower($email); // Normalize > } > } > ``` > > As of today, it can be written as: > > ```php > class User { > > /** @var non-empty-string & lowercase-string */ > public readonly string $email; > > public function __construct( > string $email, > ) { > if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { > throw new InvalidArgumentException('Invalid email'); > } > $this->email = strtolower($email); // Normalize > } > } > ``` > > With your proposal, there is no obvious way to keep the additional > information provided in the phpdoc. Maybe we could imagine something like > that: > > ```php > class User { > /** > * @param string $email the e-mail address as provided > */ > public function __construct( > /** @var non-empty-string & lowercase-string the normalised e-mail > address */ > string $email, > ) { > if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { > throw new InvalidArgumentException('Invalid email'); > } > $this->email = strtolower($email); // Normalize > } > } > ``` > > but it is obviously clearer (at least to my eyes) to keep the property and > the constructor parameter separate. > > One additional thought: Readonly properties carry a constraint that is > annoying at first, but that is finally beneficial for the clarity of code > that is written. When initialising such a property with something more > complex than what can be comfortably written in a single expression, I am > forced to write the intermediate results in a temporary variable and to > assign the final value to the property at the end of the process, instead > of transforming gradually the value of the property. The resulting code is > a few lines longer, but it is no less clear, even it is often clearer, > because it is obvious that this specific assignment supplies the final > value of the property, and there is no need to look further down to see > whether the value will undergo some additional transformations. As of > today, this “final assignment” may be part of the constructor signature; > with this RFC implemented, one can no longer know at a glance whether this > assignment is “final”. > > (Also I sympathise with Larry: rigid coding styles and static analysers’ > promoted “good practices” add problematic limitations that are not part of > the semantics of language. I prefer disabling checks in PHPStan rather than > downgrading to non-safe mutable properties and/or writing getters around > them.) > > >
Thank you for the thoughtful feedback. You raise valid points about type precision and PHPDoc annotations being harder to express with CPP. I've added a "Design Considerations" section to the RFC acknowledging these tradeoffs and clarifying when traditional declaration remains preferable (type narrowing, detailed annotations, complex initialization) vs. when CPP + reassignment fits well (simple transformations like trim/lowercase, validation with fallback). The key point is: this RFC adds an option, it doesn't mandate any style. If "final at declaration" clarity matters for a specific property, traditional declaration remains available. Regarding the "final assignment" concern: an earlier iteration considered restricting reassignment to only the constructor body (no other methods could reassign), but this was ruled out, at least for consistency with __clone(). Cheers, Nicolas

Bob Weinand

213 days ago
Hey Nicolas, On 29.1.2026 17:36:11, Nicolas Grekas wrote:
> > Thank you for the thoughtful feedback. You raise valid points about > type precision and PHPDoc annotations being harder to express with CPP. > > I've added a "Design Considerations" section to the RFC acknowledging > these tradeoffs and clarifying when traditional declaration remains > preferable (type narrowing, detailed annotations, complex > initialization) vs. when CPP + reassignment fits well (simple > transformations like trim/lowercase, validation with fallback). > > The key point is: this RFC adds an option, it doesn't mandate any > style. If "final at declaration" clarity matters for a specific > property, traditional declaration remains available.
I do generally sympathize with Claudes viewpoint here. While it's true that the RFC only adds a way how to write code, it also removes the currently valid assumption, that, whenever a readonly property is declared in a constructor arg, the arg will be exactly identical to the passed value. I will likely vote abstain on this RFC. I'm on the fence between "we should have this" (I can see that not repeating the type and variable name has some benefits) and "this muddies the readonly semantics". Bob

Marco Pivetta

211 days ago
Hey Nicolas, On Thu, 22 Jan 2026 at 16:34, Nicolas Grekas <nicolas.grekas+php@gmail.com> 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? Marco Pivetta https://mastodon.social/@ocramius https://ocramius.github.io/

Nicolas Grekas

210 days ago
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> > 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

Rob Landers

210 days ago
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>> 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

Nicolas Grekas

210 days ago
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> > 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

Rob Landers

210 days ago
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>> 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. — Rob

Nicolas Grekas

210 days ago
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> > 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?

Rob Landers

210 days ago
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>> 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

Nicolas Grekas

210 days ago
Le mar. 3 févr. 2026 à 10:30, Rob Landers <rob@bottled.codes> a écrit :
> > > 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> > 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) >
You're correct. Although such code explicitly decided to opt-in for ignoring anything from the parent, and that's always a very bad idea.
> FWIW, in my Records RFC, properties were fully mutable during
construction for exactly this reason. I wouldn't mind making readonly properties mutable during initial construction if we can get a consensus on this. I've not been brave enough to consider this was possible. I might be wrong :D

Tim Düsterhus

208 days ago
Hi On 2/3/26 10:22, Nicolas Grekas wrote:
> 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
This is a good example that as far as I can tell is not explicitly spelled out in the RFC: Please include an example where the *child* sets a readonly property defined in the parent before calling the parent constructor. class P { public function __construct( public readonly string $x = 'P', ) { } } class C extends P { public function __construct() { $this->x = 'C'; parent::__construct(); // Will this throw or not? } } More generally, as Rob, I stumbled upon the “Child Classes Can Reassign Parent Properties” section, because it's least obviously correct behavior to me. My understanding of this RFC is that it is intended to solve the case where the class itself needs to perform some “post-processing” on a promoted readonly property that it owns. Specifically, the property becomes locked once the constructor completes. For the example in “Child Classes Can Reassign Parent Properties” my mental model says that `$prop` is owned by `Parent_`, since `Parent_` is the class that declared it. Thus it would be natural for me to ”lock” `$prop` once the `parent::__construct()` call completes. If the child class needs to do special processing on the property, it has two options: 1. Not call the parent constructor. If the parent constructor logic is unfit, then not calling the constructor is the right thing rather than trying to revert part of what it did to a readonly property. 2. Call `parent::__construct()` with an appropriately modified value: parent::__construct('child override'); So to describe my expected semantics in more technical terms: The implementation should “unlock” the property after the “auto-generated promotion logic” finished and should “relock” the property when the method with the auto-generated promotion logic finishes. In other words: public function __construct( public readonly string $prop = 'parent default', ) { // Parent does NOT reassign here } should be equivalent to: public function __construct( string $prop = 'parent default', ) { $this->prop = $prop; // unlock $this->prop (set IS_PROP_REINITABLE) try { // Parent does NOT reassign here } finally { // lock $this->prop (unset IS_PROP_REINITABLE) } } With this logic, the answer to initial “will this throw” question of this email would be “yes”, because the implicit `$this->prop = $prop` assignment happens before the unlock. I believe it would also more closely match the semantics of `__clone()`. Best regards Tim Düsterhus

Nicolas Grekas

197 days ago
(Reposting per Tim's email, thanks Derick for working on this!)
> Hi > > On 2/3/26 10:22, Nicolas Grekas wrote: > > 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 > > This is a good example that as far as I can tell is not explicitly > spelled out in the RFC: Please include an example where the *child* sets > a readonly property defined in the parent before calling the parent > constructor. > > class P { > public function __construct( > public readonly string $x = 'P', > ) { } > } > > class C extends P { > public function __construct() { > $this->x = 'C'; > > parent::__construct(); // Will this throw or not? > } > } > > More generally, as Rob, I stumbled upon the “Child Classes Can Reassign > Parent Properties” section, because it's least obviously correct > behavior to me. > > My understanding of this RFC is that it is intended to solve the case > where the class itself needs to perform some “post-processing” on a > promoted readonly property that it owns. Specifically, the property > becomes locked once the constructor completes. > > For the example in “Child Classes Can Reassign Parent Properties” my > mental model says that `$prop` is owned by `Parent_`, since `Parent_` is > the class that declared it. Thus it would be natural for me to ”lock” > `$prop` once the `parent::__construct()` call completes. If the child > class needs to do special processing on the property, it has two options: > > 1. Not call the parent constructor. If the parent constructor logic is > unfit, then not calling the constructor is the right thing rather than > trying to revert part of what it did to a readonly property. > > 2. Call `parent::__construct()` with an appropriately modified value: > parent::__construct('child override'); > > So to describe my expected semantics in more technical terms: The > implementation should “unlock” the property after the “auto-generated > promotion logic” finished and should “relock” the property when the > method with the auto-generated promotion logic finishes. > > In other words: > > public function __construct( > public readonly string $prop = 'parent default', > ) { > // Parent does NOT reassign here > } > > should be equivalent to: > > public function __construct( > string $prop = 'parent default', > ) { > $this->prop = $prop; > // unlock $this->prop (set IS_PROP_REINITABLE) > try { > // Parent does NOT reassign here > } finally { > // lock $this->prop (unset IS_PROP_REINITABLE) > } > } > > With this logic, the answer to initial “will this throw” question of > this email would be “yes”, because the implicit `$this->prop = $prop` > assignment happens before the unlock. I believe it would also more > closely match the semantics of `__clone()`. > > Best regards > Tim Düsterhus >
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). 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. Anything else? Cheers, Nicolas

Tim Düsterhus

194 days ago
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

Nicolas Grekas

176 days ago
Le jeu. 22 janv. 2026 à 16:33, Nicolas Grekas <nicolas.grekas+php@gmail.com> a écrit :
> Dear all, > > Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign > > As a quick intro, my motivation for that RFC is that I find it quite > annoying that readonly properties play badly with CPP (constructor property > promotion). > > Doing simple processing of any argument before assigning it to a readonly > property forces opting out of CPP. > > This RFC would allow setting once a readonly property in the body of a > constructor after the property was previously (and implicitly) set using > CPP. > > This allows keeping property declarations in their compact form while > still enabling validation, normalization, or conditional initialization. >
Friendly reminder about this RFC. It's been quiet for 13 days so I plan to start the vote on Thursday if there are no more concerns to the text of the proposal. Cheers, Nicolas

Nicolas Grekas

168 days ago
Dear all, Here is a new RFC for you to consider:
>> https://wiki.php.net/rfc/promoted_readonly_constructor_reassign >> >> As a quick intro, my motivation for that RFC is that I find it quite >> annoying that readonly properties play badly with CPP (constructor property >> promotion). >> >> Doing simple processing of any argument before assigning it to a readonly >> property forces opting out of CPP. >> >> This RFC would allow setting once a readonly property in the body of a >> constructor after the property was previously (and implicitly) set using >> CPP. >> >> This allows keeping property declarations in their compact form while >> still enabling validation, normalization, or conditional initialization. >> > > > Friendly reminder about this RFC. It's been quiet for 13 days so I plan to > start the vote on Thursday if there are no more concerns to the text of the > proposal. >
I made some changes last week based on Ilia's review. Nothing substantial, just clarifications, and removal of the implementation notes, which don't belong to the RFC text. Here is the diff FYI: https://wiki.php.net/rfc/promoted_readonly_constructor_reassign?do=diff&rev2%5B0%5D=1771972248&rev2%5B1%5D=1773345559&difftype=sidebyside This postpones the vote by 7 days, I'll open it next week, unless the discussion continues of course! Cheers, Nicolas