On Tue, Jul 15, 2025, at 19:05, Nicolas Grekas wrote: > Not sure it's really a contribution to the discussion but in case you missed > it, you can make public properties "public(set)": > > final readonly class Response { > public function __construct( > public public(set) int $statusCode, > public public(set) string $reasonPhrase, > // ... > ) { > if($this->statusCode >= 600) { > throw new LogicException(); > } > } > } > > Then your example works. > That's ugly, but that doesn't have to stay that way. > And I'm now wondering why is that not the default? > > Nicolas
It "works" but it still results in an invalid state (statusCode >= 600 is never thrown). Previously, this kind of invalid object would only be possible via reflection and skipping the constructor. Now, we have a copy, without a copy-constructor to perform any validation or error checking before the copy completes; so basically invalid objects are incredibly easy/accidental to be made, unless everyone starts changing all validation to be done in getters/setters (which is not possible on readonly objects) or by keeping things protected(set) and ensuring that only specific functions set them... and probably make every readonly class final. — Rob