On Thu, 17 Nov 2022 at 14:47, Marco Pivetta <ocram...@gmail.com> wrote:

> Hey Máté,
>
> On Tue, 15 Nov 2022 at 07:30, Máté Kocsis <kocsismat...@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

Reply via email to