Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129946 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 5D68A1A00BC for ; Wed, 28 Jan 2026 18:53:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1769626425; bh=D6APoL13APF+dlJeCjjNY/6SQsokBxcipczbJOBUN2M=; h=From:Subject:Date:In-Reply-To:Cc:To:References:From; b=gNQbXihYY9+EOVn0bYLBkYJUMnwY8NVa8VuPjE37t4PrUQ9K3o2CHnf5uhsEdbKrz 2+LEB9UgV7+QoD4c9B7jhUyFpwUITggqc7jQl8SU0bwi+/dJvOEM5Ab39D44SIRXYk Y2fKatKF0pgQ6xlnlP95V/0AL0iW3qy+RhVq6Uj4ncB73bDqWoCzqpE0db/gU9wv0M SlXhpJlfmxVxom3l6HXKaU0RmXsM3gtXFCbQm0u+qSADW2cmsLanu6yazuIIh4gmiY C2R28bXHU05qTHVP6lT+af+Rsic6TYG7e+HfwwNjnn6z6/o7urLGAlOiY4VMIbencZ u31LZxCI50ycQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id CF1D5180088 for ; Wed, 28 Jan 2026 18:53:44 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from mail-wr1-f66.google.com (mail-wr1-f66.google.com [209.85.221.66]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 28 Jan 2026 18:53:44 +0000 (UTC) Received: by mail-wr1-f66.google.com with SMTP id ffacd0b85a97d-42fed090e5fso173063f8f.1 for ; Wed, 28 Jan 2026 10:53:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769626418; x=1770231218; darn=lists.php.net; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:from:to:cc:subject:date:message-id:reply-to; bh=9RymwSIRuWNHD0HWwxNcq0CAGlglD73KD47DK5bGVdI=; b=AMNRfQBqjDlwl1eO5Fq546tqkm+iJZZWOPSBdBFfgAx0EHZr5hBwQK0QRExkykgldn lg+FEmJ5U8mqgMV+2wsPp29mPtmtg7e0OPiO4Xzyr2r1VBOtJNGk9N4+fxJPAXyFnRUP EYLkh9LesNssHjeWtBLasVn8qx+38oAnJJSuXAHXY9Ktks/RLyAQJerBy8aJKRUQLESU mWB/wABDd4/CyOioi84xOeL6yGTtjv1BDQicHGA/EyEdAJCcFucs2VGYiW2Fr7H333hA fjET9iAaarS9835cpxdnlpfc4czaAIHNiC7ZmiI3YaIrBNyrLt4wTgevi5C6sIi6MpuD Ytkg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769626418; x=1770231218; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=9RymwSIRuWNHD0HWwxNcq0CAGlglD73KD47DK5bGVdI=; b=Dh6O2LffXJl7sjFxzvcBGvEuNBXISwt2TVT1+BeIIKAO72aGfX8wI80z/iLWeQMxwl 9uuGF7eJ4T1SD6gOBmJw8jldoyXVrxclW9yhGHwMT+tMME4p0Z/RqsRXqh26nVkeGISE HfKxdlDqn01Cc1p2HqWctmVvhQg59IDN5/IfjkhwNvaNmXNW1RNjxq19lrGM81aW1iiF DOLw0UBBXg61vQWSsL/XdhwpnaBwEk2hMA7GWfoibWueav4K8VR6ivBl3g+d9l7PPxEs SILiSmQjsw6rkAa2h3/8tCUYoeDrSu8w87bVpBMwUjRLk8pPh2jWXwDfqtdmebkPHYHM 0Jlw== X-Gm-Message-State: AOJu0YzDOuWLRe2P2k1pXTTyWiDBIEq2BQ/W9WpKaGrQjqB5Vb+rsIew eo38V7UAGaaXK50AX+sMUC9iSvFgktXpJeYh5bL9OJKjU83PSCwAxV+zfo4iybnX X-Gm-Gg: AZuq6aKeIjc/Q2sLJ7Yw5Hqw8DHRbJPD2JQQ3Tfy+NOHSjqw2Um7fLyocL+PAx1oVUD +7yeVa8op4OiFjlKV9l4kjTDnfSste+gActsg6kkZRoyuubqyuEJIryHi3HfCni3JYhaRAUwfTA 5kI8qKQSGIWNFJOHVZnytPxzEfKABc2Udu7xQxPU4ew5iGho64d2YLbg5V/jYBWWARQKd4xVe8Y aQmZ/WeCUjTUywmjI4O7Q3xlz5ROdkbIrads+seARHs8TR7hXVgnalpsN148AQk9yASspaTGJb+ G8dsey/TfBO+PsFDBUQdjpuNJ35M9Nf7CYGIiQv+nb+732xNkZmnRXa/tcb51HX85bmkaurNDU0 7bdfk4VlS6qaKFbi9AGDUru7L+utHElGBJbQnbsIvosa90dE5AP4iXf8xTUw7Jr7B0Y2Hc+Iu+N GQdIOfAPOqqbn7iOZOUv79ExcZV+66MXFmLg== X-Received: by 2002:a05:6000:2510:b0:435:a3c5:a526 with SMTP id ffacd0b85a97d-435dd02f471mr8340521f8f.14.1769626418003; Wed, 28 Jan 2026 10:53:38 -0800 (PST) Received: from smtpclient.apple ([89.249.45.14]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-435e10edfccsm8459695f8f.17.2026.01.28.10.53.37 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 28 Jan 2026 10:53:37 -0800 (PST) Message-ID: Content-Type: multipart/alternative; boundary="Apple-Mail=_8F5D2E20-F8D6-4C71-A1DA-297255858F83" Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.300.41.1.7\)) Subject: Re: [PHP-DEV] [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor Date: Wed, 28 Jan 2026 19:53:26 +0100 In-Reply-To: Cc: PHP Internals List To: Nicolas Grekas References: X-Mailer: Apple Mail (2.3864.300.41.1.7) From: claude.pache@gmail.com (Claude Pache) --Apple-Mail=_8F5D2E20-F8D6-4C71-A1DA-297255858F83 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > Le 22 janv. 2026 =C3=A0 16:33, Nicolas Grekas = a =C3=A9crit : >=20 > Dear all, >=20 > Here is a new RFC for you to consider: > https://wiki.php.net/rfc/promoted_readonly_constructor_reassign >=20 > 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). >=20 > Doing simple processing of any argument before assigning it to a = readonly property forces opting out of CPP. >=20 > 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. >=20 > This allows keeping property declarations in their compact form while = still enabling validation, normalization, or conditional initialization. >=20 > 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=E2=80=99s illustrate that with two = examples from the RFC. First: ```php class Config { public function __construct( public readonly ?string $cacheDir =3D null, ) { $this->cacheDir ??=3D sys_get_temp_dir() . '/app_cache'; } } ``` As of today you can write: ```php class Config { public readonly string $cacheDir; public function __construct( ?string $cacheDir =3D null, ) { $this->cacheDir =3D $cacheDir ??=3D 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 =3D 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 =3D 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 { /**=20 * @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 =3D 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 =E2=80=9Cfinal assignment=E2=80=9D = may be part of the constructor signature; with this RFC implemented, one = can no longer know at a glance whether this assignment is =E2=80=9Cfinal=E2= =80=9D. (Also I sympathise with Larry: rigid coding styles and static = analysers=E2=80=99 promoted =E2=80=9Cgood practices=E2=80=9D 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.) =E2=80=94Claude --Apple-Mail=_8F5D2E20-F8D6-4C71-A1DA-297255858F83 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8

Le 22 janv. 2026 =C3=A0 16:33, Nicolas Grekas = <nicolas.grekas+php@gmail.com> a =C3=A9crit :

Dear = all,

Here is a new RFC for you to = consider:

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=E2=80=99s illustrate that with = two examples from the RFC. = First:

```php
class Config = {
    public function __construct(
  =       public readonly ?string $cacheDir =3D = null,
    ) {
        = $this->cacheDir ??=3D sys_get_temp_dir() . = '/app_cache';
    = }
}
```

As of today you can = write:

```php
class Config = {
    public readonly string = $cacheDir;

    public function = __construct(
        ?string $cacheDir =3D = null,
    ) {
        = $this->cacheDir =3D $cacheDir ??=3D 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 =3D 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 =3D = 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 =3D 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 = =E2=80=9Cfinal assignment=E2=80=9D may be part of the constructor = signature; with this RFC implemented, one can no longer know at a glance = whether this assignment is =E2=80=9Cfinal=E2=80=9D.

(Also I sympathise with Larry: rigid coding styles and static = analysers=E2=80=99 promoted =E2=80=9Cgood practices=E2=80=9D 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.)

=E2=80=94Claude

= = --Apple-Mail=_8F5D2E20-F8D6-4C71-A1DA-297255858F83--