Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor
| From: | Nicolas Grekas | Date: | Thu, 29 Jan 2026 16:36:11 +0000 |
| Subject: | Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129950@lists.php.net to get a copy of this message | ||
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