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:
class Config {
public function __construct(
public readonly ?string $cacheDir = null,
) {
$this->cacheDir ??= sys_get_temp_dir() . '/app_cache';
}
}
As of today you can write:
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:
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:
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:
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