Hi Claude,

Le mer. 28 janv. 2026 à 19:53, Claude Pache <[email protected]> a
écrit :

>
>
> Le 22 janv. 2026 à 16:33, Nicolas Grekas <[email protected]> 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

Reply via email to