Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129950 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 2F3FB1A00BC for ; Thu, 29 Jan 2026 16:36:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1769704589; bh=jFLPye2YK5qIqoQGYoaoBEFQgfaYyjEgEnqqP4440hU=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=jARaaQMMsj1/6ca2iFpdMUzk31//keJcdsj+lcXSJoCtcQL5XC4OHA0RdNI9ojKN0 lEsR8t2v0DPvp5y75Q0Xd8sKhAjOp9fVtoKWSCx9bLRZckYEZoPfpXuaVxvVrT0tPH ZCCZUep3ByDW5h/spdjlUSv2vswHUak8dm0JCXnthABKEBFSYZG2bTusvFXVsG1UtD HqsG+OFhstVpRbfHSl8EgLadoXSKBJGwLTwCBbKaQwpJIYgJQkh7ypCdNOtXGgMHHB qf5PruLeysgWJwqpXimzfglMdFbRi9VlHAwzwuppdM3VsqHspmKLCm62YVdbQzI7U4 2t5wxTJkymKdg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 6109E1801D4 for ; Thu, 29 Jan 2026 16:36:28 +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=1.6 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_50, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS, FREEMAIL_FROM,FREEMAIL_REPLY,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-qv1-f49.google.com (mail-qv1-f49.google.com [209.85.219.49]) (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 ; Thu, 29 Jan 2026 16:36:28 +0000 (UTC) Received: by mail-qv1-f49.google.com with SMTP id 6a1803df08f44-88a2e3bd3cdso14941766d6.0 for ; Thu, 29 Jan 2026 08:36:23 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1769704582; cv=none; d=google.com; s=arc-20240605; b=V7zQQe6nW2VnIdw1nIkYwm4vFou+bg3i+XJfccji7EPP1L1OGF/qPYU36dJl5LFRSI yIexj1xhrn52sklbLtAS7I793PVgLhUgVRsZ8jZt8oQ+/ah8AuJFuQCp/2ncCAUbgZYL r4kfHNUKX46KDyP4aEdJvSbffVt4j9Qq9WBMTewAshF9yQsCpmK0uqe4/2+YI52r1FkP BOmMLblFcMy62MOFXT2jtaLvRkdP1apYTJI9vs1CivY3mfp6ntuzVDtUmHZgV3JlifaP vbz4nCPSEEfOhvzxIXI6Uiz3JBVfkEVuZ+n4tGEgzoBUehvyCEtu1IxqGHkRfLTcvJji cP2g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=nF+iuetdnuaVprlHeuLDygDMFmZFd8w3TlhgKMevYDo=; fh=5VU0FKOYVSOWNmMge6rL4AFHEgd5bsJakKASQP/aAZY=; b=jw8OoyqVPX8yBmTnjYvIt67dsaOQ9o+a7Zje+OuHaxhrp4KctsCIMu23/cG0MSjhBy 7WKPSt3YvCVXFuPcqfJP2lioAlshXXrsxcaOCUARRtHhBjlVyhCOyiaCRRW1o4VHC35r 5CkM4B4lr8Vz31/SH37LBue7abZ2o/uCH7UD11wAvKS3jMFDlwpYof2CiaOhmxkHeQuo 1QUOEMB7eCCx/L9KV9TTDub9mDVT4TzQrg2bsEKE4Kp6cUiUnUdXmo2NlLG3CGI45KhC xCOr/OvLHc0li2iMVHgODoT1rqwjyGPvs4lsLEYJ6T1SsxHwEGrbP2snf9ThMD46UNWC UjkQ==; darn=lists.php.net ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769704582; x=1770309382; darn=lists.php.net; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=nF+iuetdnuaVprlHeuLDygDMFmZFd8w3TlhgKMevYDo=; b=TzQKrISgNP3geyN3BD3n77/+YrvJ2cNxo0GVlt/0hcclp2Aw3OoRBtUObBWYMQAYHa em4stBJ0IzBe2ZPGOZqkO/2Qv0e2R55MlaIAILvU1YqomnUrFPkhpPaWntYy8IuOLjy2 hJ8nDuw4mdX+cVu1DRPyuOatSMobJTFY0S6aKFu7i5a/UYTEZ2ucz2zarQY9HeDKnIBR zEsTGOmE0Sy2qf9I3gcU8H06a6OIlOZHtomJzI7wvlb6uYbiUMYi+z7E1oM1tw831edL 4XrNfjIYdsUYnUfhMplew+IT7MWTp9WgYxF8XXGzdoEBvHqdtToAHRytSFxAUyKZWRFZ 2vHg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769704582; x=1770309382; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=nF+iuetdnuaVprlHeuLDygDMFmZFd8w3TlhgKMevYDo=; b=cbCyGFAihG6DmUb4wVEOj0hXiLUjfIjj3XsMjnIQSQOlgFgbUHnk5nyN1vZqfa0qQE Zpx2o+OvyG3E1OvIRphr8jdNXZtJk6JGukHgzMvGhfM8zBqlN3s5viKykboK4gfjvJqk SI4fjc0t8VZjjVPqcG0/+Q+wtIS/zNlVukdBYsuXR/XJBPfQjF+3ZeamY/09hvATl7R1 Bs1LgJu/RIVwJjb1cCV3VHwpT/Sada7tOyU7x9AssXhbs0cSCrj6fXtNrD86a7neDGJC eZAV/qc9dl+8vrfA90x3PJM8Nk/BfwstqwjvaRaeEnc2F4JKImM9pi5xdANEshjJML/5 9Mrg== X-Gm-Message-State: AOJu0YxbTBL2tu9NEQM+X5gZ9Tc7nyy6qYZvnjAtyV9gy1lbJgVBb+XX lsLln3yhxsuzdV/Zb/0wQrtWnaMOLFbi3pvPDxFG8NUdEzchSxk/roW9UrX561eZFsWdbg7aJMz /u3NajsZFJik+CftuX2oluyeGJb/bgnNOW9/VAJs= X-Gm-Gg: AZuq6aJ9pPH3cJkRh0y69jbjOLXxGc1rjAkQihHjTfTPPaqsbXu8pccpDZcLofGDOco Q0EibxxsvbHprYChGlRAE0kY6/h4sKKEr5ZdfQbPpjQ1SUM33BcTVHRTnSWesBUhggyDA6SkTeU glsFXySbA0SOJJrUfGjMNhx1IPVPazH9yDjs862rFxdL420qppOEeyzlCVvK0M6JdgpL87Mf/jV 3n2cqgykZFD4J309cTHFpbfbYIguUVaYu0r0GsEkK9wQQExKq/2lyRzecXFMM3qZXy9Ph6/JIGx Zn7j75LZqgLSicx2OfYyc65fyeBM2U0VjNLGb2P6bwUIcfquOv8dA2iuB/sCXoYM3J2IWefk9Yj +891ZTMi4aUHKrzd+AYZEVE9s X-Received: by 2002:a05:6214:2b0e:b0:894:7eb6:72e5 with SMTP id 6a1803df08f44-894df891155mr54159596d6.0.1769704582181; Thu, 29 Jan 2026 08:36:22 -0800 (PST) Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Thu, 29 Jan 2026 17:36:11 +0100 X-Gm-Features: AZwV_QjYGY7w4X9tgO3iJT8aMeu6LgOYPzi4dR2t0dwGSflr_rL-SJok0os305I Message-ID: Subject: Re: [PHP-DEV] [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor To: Claude Pache Cc: PHP Internals List Content-Type: multipart/alternative; boundary="000000000000fa772b0649897414" From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --000000000000fa772b0649897414 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi Claude, Le mer. 28 janv. 2026 =C3=A0 19:53, Claude Pache a =C3=A9crit : > > > Le 22 janv. 2026 =C3=A0 16:33, Nicolas Grekas a > =C3=A9crit : > > 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 proper= ty > 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 expens= e > 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_ca= che'; > } > } > ``` > > Note that the property is marked as non-nullable, a precision that may be > useful for both programmers and static analysers. With your proposal, the= re > 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-mai= l > 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 an= d > 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 i= s > 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 constru= ctor signature; > with this RFC implemented, one can no longer know at a glance whether thi= s > 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 tha= t are not part of > the semantics of language. I prefer disabling checks in PHPStan rather th= an > 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 --000000000000fa772b0649897414 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi Claude,

Le=C2=A0m= er. 28 janv. 2026 =C3=A0=C2=A019:53, Claude Pache <claude.pache@gmail.com> a =C3=A9crit=C2=A0:
=


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

Dear= all,

Here is a=C2=A0new RFC for you to consider:<= /div>

As a quick intro, my motivat= ion for that RFC is that I find it quite annoying that readonly properties = play badly with CPP (constructor property promotion).

Doing s= imple processing of any argument before assigning it to a readonly property= forces opting out of CPP.

This RFC would allow setting once a reado= nly 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, normali= zation, or conditional initialization.

Cheers,
Nicola= s


Hi,

I am reserved about the proposal, because this style of using CPP a= nd processing the value after the fact tends to favour brevity at the expen= se of precision and clarity. Let=E2=80=99s illustrate that with two example= s from the RFC. First:

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

As of today you can writ= e:

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

=C2= =A0 =C2=A0 public function __construct(
=C2=A0 =C2=A0 =C2=A0 =C2= =A0 ?string $cacheDir =3D null,
=C2=A0 =C2=A0 ) {
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 $this->cacheDir =3D $cacheDir ??=3D sys_get_tem= p_dir() . '/app_cache';
=C2=A0 =C2=A0 }
}
=
```

Note that the property is marked as non-n= ullable, a precision that may be useful for both programmers and static ana= lysers. With your proposal, there is no way to keep this information.
=


The second example is similar:

```php
class User {
=C2=A0 =C2=A0 publi= c function __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 public readon= ly string $email,
=C2=A0 =C2=A0 ) {
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 throw new InvalidArgumentException(&= #39;Invalid email');
=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 $this->email =3D strtolower($email); =C2=A0/= / Normalize
=C2=A0 =C2=A0 }
}
```
<= br>
As of today, it can be written as:

`= ``php
class User {

=C2=A0 =C2=A0 /** @va= r non-empty-string & lowercase-string */
=C2=A0 =C2=A0 public= readonly string $email;

=C2=A0 =C2=A0 public func= tion __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 string $email,
=C2=A0 =C2=A0 ) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!filter_v= ar($email, FILTER_VALIDATE_EMAIL)) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 throw new InvalidArgumentException('Invalid email');<= /div>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 =C2=A0 =C2= =A0 $this->email =3D strtolower($email); =C2=A0// Normalize
= =C2=A0 =C2=A0 }
}
```

With you= r proposal, there is no obvious way to keep the additional information prov= ided in the phpdoc. Maybe we could imagine something like that:
<= br>
```php
class User {
=C2=A0 =C2=A0 /**=C2= =A0
=C2=A0 =C2=A0 =C2=A0* @param string $email the e-mail address= as provided
=C2=A0 =C2=A0 =C2=A0*/
=C2=A0 =C2=A0 publi= c function __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 /** @var non-= empty-string & lowercase-string the normalised e-mail address */
<= div>=C2=A0 =C2=A0 =C2=A0 =C2=A0 string $email,
=C2=A0 =C2=A0 ) {<= /div>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!filter_var($email, FILTER_VALIDA= TE_EMAIL)) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 throw new = InvalidArgumentException('Invalid email');
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $this->email =3D s= trtolower($email); =C2=A0// Normalize
=C2=A0 =C2=A0 }
}=
```

but it is obviously clearer (at lea= st 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 wit= h something more complex than what can be comfortably written in a single e= xpression, I am forced to write the intermediate results in a temporary var= iable and to assign the final value to the property at the end of the proce= ss, instead of transforming gradually the value of the property. The result= ing code is a few lines longer, but it is no less clear, even it is often c= learer, because it is obvious that this specific assignment supplies the fi= nal 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 toda= y, this =E2=80=9Cfinal assignment=E2=80=9D may be part of the constructor s= ignature; with this RFC implemented, one can no longer know at a glance whe= ther 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 limitation= s 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 w= riting getters around them.)



Thank you for the thoughtful feedback. Yo= u raise valid points about type precision and PHPDoc annotations being hard= er to express with CPP.

I've added a "Design Consideratio= ns" section to the RFC acknowledging these tradeoffs and clarifying wh= en traditional declaration remains preferable (type narrowing, detailed ann= otations, complex initialization) vs. when CPP + reassignment fits well (si= mple transformations like trim/lowercase, validation with fallback).
The key point is: this RFC adds an option, it doesn't mandate any styl= e. If "final at declaration" clarity matters for a specific prope= rty, 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,=C2=A0=C2=A0at least=C2=A0for consistency with __= clone().

Cheers,<= /div>
Nicolas

= =C2=A0
--000000000000fa772b0649897414--