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