Re: [RFC] [Discussion] Readonly class amendments

From: Date: Sat, 26 Nov 2022 22:56:28 +0000
Subject: Re: [RFC] [Discussion] Readonly class amendments
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-119037@lists.php.net to get a copy of this message
On Thu, 17 Nov 2022 at 14:47, Marco Pivetta <ocramius@gmail.com> wrote: > Hey Máté, > > On Tue, 15 Nov 2022 at 07:30, Máté Kocsis <kocsismate90@gmail.com> wrote: > > > Hi Everyone, > > > > Following Nicolas' thread about "Issues with readonly classes" ( > > https://externals.io/message/118554), we created an RFC to > > fix two > issues > > with the readonly behavior: > > https://wiki.php.net/rfc/readonly_amendments > > > > > Since I was reviewing https://github.com/lcobucci/jwt/pull/979 > today, I > had > to put the "why immutability is an LSP requirement" in examples. > > As explained in > > https://github.com/lcobucci/jwt/pull/979#discussion_r1025280053, a > consumer > relying on a readonly implementation of a class probably also > expects > replacement implementations as read-only. > I am aware that we lack the readonly marker at interface level > (would be > really neat), but the practical example is as follows: > > > ```php > <?php > > /* readonly */ class ImmutableCounter { > public function __construct(private readonly int $count) {} > public function add1(): self { return new self($this->count + 1); } > public function value(): int { return $this->count; } > } > > // assuming the proposed RFC is in place, this is now possible: > class MutableCounter extends ImmutableCounter { > public function __construct(private int $count) {} > public function add1(): self { return new self(++$this->count); } > public function value(): int { return $this->count; } > } > > $counter1 = new ImmutableCounter(0); > $counter2 = $counter1->add1(); > $counter3 = $counter2->add1(); > > var_dump([$counter1->value(), $counter2->value(), $counter3->value()]); > > $mutableCounter1 = new MutableCounter(0); > $mutableCounter2 = $mutableCounter1->add1(); > $mutableCounter3 = $mutableCounter2->add1(); > > var_dump([$mutableCounter1->value(), $mutableCounter2->value(), > $mutableCounter3->value()]); > ``` > > This prints ( https://3v4l.org/IDhRY ) > > ``` > array(4) { > [0]=> > int(0) > [1]=> > int(1) > [2]=> > int(2) > } > array(4) { > [0]=> > int(1) > [1]=> > int(2) > [2]=> > int(2) > } > ``` > > I took a simplified example on purpose, just to demonstrate the problem > with readonly disappearing in child classes. > > That's really really confusing, buggy, and, from a consumer PoV, broken > (LSP violation too, transitively). > Removing the readonly restriction for a child class is *not* an LSP violation. As all the invariants will be maintained in the child classes (i.e. all the properties of the parent class remain readonly). Moreover, a consumer of the base class can only rely on the API defined by said class, as such it will not be aware of any mutable properties defined by a child class. LSP is really not about the class hierarchy/subtypes itself, but the relation of input data and output data of the operations allowed on a type (i.e. method calls or property access), and because readonly is not the same as immutability there is no theoretical issue. Now, I can agree that it can be slightly surprising that a class which has a writeable property extends from one which only has readonly ones, but one can already immitate this by declaring every property readonly but not the class. Best regards, George P. Banyard

« previous php.internals (#119037) next »