Newsgroups: php.internals
Path: news.php.net
Xref: news.php.net php.internals:118604
Return-Path: <nicolas.grekas@gmail.com>
Delivered-To: mailing list internals@lists.php.net
Received: (qmail 7303 invoked from network); 10 Sep 2022 21:35:09 -0000
Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5)
  by pb1.pair.com with SMTP; 10 Sep 2022 21:35:09 -0000
Received: from php-smtp4.php.net (localhost [127.0.0.1])
	by php-smtp4.php.net (Postfix) with ESMTP id AE4B01804AC
	for <internals@lists.php.net>; Sat, 10 Sep 2022 14:35:08 -0700 (PDT)
X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net
X-Spam-Level: 
X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED,
	DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE,
	RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS,
	T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.2
X-Spam-ASN: AS15169 209.85.128.0/17
X-Spam-Virus: No
X-Envelope-From: <nicolas.grekas@gmail.com>
Received: from mail-yw1-f180.google.com (mail-yw1-f180.google.com [209.85.128.180])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256)
	(No client certificate requested)
	by php-smtp4.php.net (Postfix) with ESMTPS
	for <internals@lists.php.net>; Sat, 10 Sep 2022 14:35:08 -0700 (PDT)
Received: by mail-yw1-f180.google.com with SMTP id 00721157ae682-3450a7358baso57434527b3.13
        for <internals@lists.php.net>; Sat, 10 Sep 2022 14:35:08 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=gmail.com; s=20210112;
        h=cc:to:subject:message-id:date:from:in-reply-to:references
         :mime-version:from:to:cc:subject:date;
        bh=57wnPF0kRtxeF+G9ERfsW5Cu30Azoi4cAiWVrf73lNA=;
        b=mbbaiDZJoAaBoO6gxpKnZpQ7SF3vES26tiPMQB5oZ/FrAa/djfnmzBmoVIjrZKynod
         vOedmDUU8MI4VaFD3845ztLvlSqAGqBbJVZBc7yQr/gaFXiBQyHBqCTA9yBEK+/7yndL
         tERtgEce/6I99lXHAJeEUqEXHaxkU6nRM+7WV1wPiY9cwZqKP09roa2I1+/fPqIA9nz2
         wlN2vT1MMGfDQFE/wfzpV0dosSFaUqgMVEPyxH+IKxG/5drMR3w72+t0t2meUOZsuzDF
         FGJ3q5hIS8Ihfnl9BPC0+wbq6eo6ineS8ZkCNvVdlsX0VXgyVsUsckTrYFY81pUaP+rJ
         NWEw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20210112;
        h=cc:to:subject:message-id:date:from:in-reply-to:references
         :mime-version:x-gm-message-state:from:to:cc:subject:date;
        bh=57wnPF0kRtxeF+G9ERfsW5Cu30Azoi4cAiWVrf73lNA=;
        b=tv5GW5WBm4T27YHgEjByntA5uvHqL2O6GwaXJ1NDvsyiXMO52mBz3y9QqRI2lyA5CU
         mACRMvFDfRRHXPCZkol6VYrw0BD078DkD1ALhA6zqSfR0nAccpzXkpkTeqyfaMhPYsZr
         /3trrtHY5UTrKOSw39Oh7dBfZH3g3NLipN/k0x32ZkuqpGw+OgSBssViJrVYbS+CJaDn
         BTYtWQ/nEVlhE8CtjMTbMrXBux2fkrbL4/qEjEjsPXqs97U/7NozM9+5fVoK+QgoR11o
         SMRuqfPr6H/UiLVwmOl3Lmh8FMYtolejj/j2wlVHBav3QIk16HlU0vIglM+yo+TnRDv3
         56/Q==
X-Gm-Message-State: ACgBeo23H6BFzVn++G7lb+ueicBtMn7IaGYe3LznS6LPt/v2UhYRBN+k
	sowhl/h9kLp5T1iHBis8+aR2SdrHij0+eQOdejY=
X-Google-Smtp-Source: AA6agR5LvfDvIs4U7h7acGvjHa1CnjL4eYkOUaas/V5X/8IXLzn871KQzMOpsq/uTlD/+OQEPwBHQxAJS6kdkSZcig4=
X-Received: by 2002:a0d:e210:0:b0:345:107c:613a with SMTP id
 l16-20020a0de210000000b00345107c613amr16966448ywe.473.1662845707243; Sat, 10
 Sep 2022 14:35:07 -0700 (PDT)
MIME-Version: 1.0
References: <CAOWwgpk7uAkZD_CH5inbm8Ng8iVL5SZsz59Mpzgpc_iG42ei8Q@mail.gmail.com>
 <CAH5C8xU=eukkqn+PToG4-JT0vqjChhkY=kd_4t_ki9mXu1UMqQ@mail.gmail.com> <c20b9aae-fbbf-484e-b73b-b370e4783ada@www.fastmail.com>
In-Reply-To: <c20b9aae-fbbf-484e-b73b-b370e4783ada@www.fastmail.com>
Date: Sat, 10 Sep 2022 23:34:55 +0200
Message-ID: <CAOWwgpkp0ndSgnS4aOiWqf+omcfT0_AiP0P35SC4hDZw0KDoHw@mail.gmail.com>
To: Larry Garfield <larry@garfieldtech.com>
Cc: php internals <internals@lists.php.net>
Content-Type: multipart/alternative; boundary="000000000000b2556d05e8596fa8"
Subject: Re: [PHP-DEV] Re: Issues with readonly classes
From: nicolas.grekas+php@gmail.com (Nicolas Grekas)

--000000000000b2556d05e8596fa8
Content-Type: text/plain; charset="UTF-8"

Hello Larry,


> Regarding your main question: I understand your problem with readonly
> > classes, and I'd be happy if we found a solution which fits your
> use-cases
> > and keeps consistency for the engine at the same time. To give you more
> > context about the inheritance related restriction in the RFC: I went with
> > forbidding the extension of readonly classes by non-readonly ones so that
> > the invariants of the parent (no dynamic or mutable properties are
> allowed)
> > don't occasionally get violated by the child. However, I cannot think
> about
> > a proper scenario now where this would cause any problems... I'm
> wondering
> > if anyone could come up with one?
>
> I don't have a real-world example, but a general pattern.  The advantage
> of a readonly object (either conventionally like PSR-7 or enforced by the
> `readonly` keyword) is that you can store a reference to it without having
> to worry about it changing out from under you.  That means you can code on
> the assumption that any data derived from that object is also not subject
> to change.
>
> If you accept a readonly object of type Foo, you would write code around
> it on that assumption.
>
> If you're then passed an object of type Bar extends Foo, which has an
> extra non-readonly property, that assumption would now be broken.  Whether
> that counts as a Liskov violation is... a bit squishy, I think.
>
> Where that might be a problem is a case like this:
>
> readonly class Person {
>   public function __construct(public string $first, public string $last) {}
>
>   public function fullName() {
>     return "$this->first $this->last";
>   }
> }
>
> class FancyPerson extends Person {
>   public string $middle = '';
>
>   public function setMiddle(string $mid) {
>     $this->middle = $mid;
>   }
>
>   public function fullName() {
>     return "$this->first $this-middle $this->last";
>   }
> }
>
> class Display {
>   public function __construct(protected Person $person) {}
>
>   public function hello() {
>     return "Hello " . $this->person->fullName();
>   }
> }
>
> Now, Display assumes Person is readonly, and thus fullName() is
> idempotent, and thus Display::hello() is idempotent.  But if you pass a
> FancyPerson to Display, then fullName() is not safely idempotent (it may
> change out from under you) and thus hello() is no longer idempotent.
>
> Whether or not that problem actually exists in practice is another
> question.  And to be fair, the same risk exists for conventionally-readonly
> classes today (PSR-7, PSR-13, etc.), unless they're made final.  Arguably
> the safety signaling of a lexically readonly class is stronger than for a
> conventionally readonly class, so one would expect that to not be broken.
> But that's the case where a non-readonly child of a readonly parent can
> cause problems.
>

Thanks for constructing this example, that's good food for thoughts.
Unfortunately, The code following readonly child class shows that this
safety you describe doesn't exist:

readonly class FancyPerson extends Person {
  private readonly stdClass $middle;

  public function setMiddle(string $mid) {
    $this->middle ??= new stdClass;
    $this->middle->name = $mid;
  }

  public function fullName() {
    return "$this->first $this-middle $this->last";
  }
}


> Personally I'm undecided at the moment whether or not it should be
> allowed.  I'm sympathetic to the "it's easier to disallow and allow later
> than vice versa" argument, but still not sure where I stand.  The above at
> least gives a concrete example of where the problem would be.
>

If we want the safety you describe, we might want a stronger version of it.
Let's name it immutable. An immutable property/class would be like a
readonly property with the additional restriction that only an immutable
value could be assigned to it (scalars + array + immutable classes.) But
that's another topic.

Your example made me doubt for a moment, but without any convincing
purpose, this should help decide that child classes shouldn't have to be
readonly.

Nicolas

--000000000000b2556d05e8596fa8--