Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127629 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id AACB11A00BC for ; Mon, 9 Jun 2025 09:51:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1749462596; bh=IuYlXrCfgE75trYnlVLslT1WbJOjrgV1Aajg2D1dD6Q=; h=From:Subject:Date:In-Reply-To:Cc:To:References:From; b=hjuGEMV1pYWMf+kgkoDbigIHi6ePMSSq5aTl/wrXlqOgcoVfMXHiDPER2DgCPd/Fe ByripCkJy12t5e7ZnIm1Mk5911Vo01IgACWQ/TFLNBJaawDfa8pzeqpLXFPrMyCEMm GdH2hDXYUQ20FZ6Ew3rXY9xJj3XBW+N7lhA3oL+b+Y0MJbd9xzmXicm3gT7wHqnm+X lORhmQpqXUjhS0JwNFSQzVwx1lmnr+NXCg4nVbBAsriiqb/w8uVVTicYobRsfkHC4b v8yUwLGYKVrPNYsK/OHQH2+A/LfRE9rq/9s9PsePIxZdHQPTsOt5EZ+l3qZMd/d4Xj 2Ai1f2YzaxAhg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 94F8A1801C7 for ; Mon, 9 Jun 2025 09:49:55 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.2 required=5.0 tests=BAYES_20,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from mail-ej1-f53.google.com (mail-ej1-f53.google.com [209.85.218.53]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Mon, 9 Jun 2025 09:49:55 +0000 (UTC) Received: by mail-ej1-f53.google.com with SMTP id a640c23a62f3a-ade326e366dso382054466b.3 for ; Mon, 09 Jun 2025 02:51:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1749462716; x=1750067516; darn=lists.php.net; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:from:to:cc:subject:date:message-id:reply-to; bh=4nt19bw6mlT+V/yyTzGMdwq4wxwn5tEVPjB/DLRdgbM=; b=mAdylR/bNwWoWMI8njReiMF1FFXhjRbCmtlc/pqGJrpmdIIOKyiXR1fXQ7lt4cTcdM 6rJZ0Cu7eK02HkIV/dy8kf7FboOwlv5LVTER6LMdogxmecoJ89T0xYxJhgRxO5L57HFt stYSHslMINfjVSEiw6/Ke6QAAAZoPCuabVywbDSLmFc8OkUSCmoqfnQRGXRBXeOQLYbk yI0k/yvMuVgzxfrQlGooME9yUe53fOLcCp5MbqVJuK/hxZCMPjC+Fmp6W7zexY1IQeQw o47PiZAb2xTT6M0feUCm/bmuWWm63721Qe2qtN4oiDtl1IngFoCnUBEj+QxtSksAIkEu JM0Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1749462716; x=1750067516; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=4nt19bw6mlT+V/yyTzGMdwq4wxwn5tEVPjB/DLRdgbM=; b=WaXd6xFK4mHSfaNkHACx6sCkCTWyXfTryWWgS2OIKgxEWBMTd+HrtC2ZvKsKtuwqWn 1HsnNTfHxtrCwLM19EiyDFGJvb9V02lF+v1xEXc/hB0+Sm1At8Xj68grzeOP8G50XuhQ 8+7i2GiIUuCcSPnFz+1abZeK9bF02TovItAOY2jkgwRbElHcTCP5VmGw279Y622EIlpD qQR55LwbbnE9r7sbB6fS4OjNiSLlODlvN1wRK1UN1dXTlVh7TcHY6tRHRdF7r5ViRC5q qlfM/93sxOCBcQWUQkjIgMY6eoAaAikjWSCZiA0GOpYXZqZPoiIABv+J4IvW6CiXsLj9 JJQg== X-Gm-Message-State: AOJu0YyTTV6k7JqCBdn/BOW8uKAg0ZYsnn4Fk/KMpU2aqkaGUL1su4KO /VY1CLRh8d+SRqH4w5btL6+5HTUExapAwJtHu0T94awkP6GwpwaQ6okRyrVWRQ== X-Gm-Gg: ASbGncvq1lfHLEZ93hNUqNlxdCDe3hYZmCxuyag6MjXIK1KoHZPjpe4zi3kbQJ7qzWv 5BP6Iqa8vMH9kNRmPGjhsOE1xcC8h0625yJLI/lZgl008OCXhqimzB3sNiirxEcEbjo51IxcQHp xM/ucJGSROmEPVPSKutjsHbdeSBFDVlF5cqY/bsX7GlB4aIvXp7k3+WNOjxC8SgP7hWjqBSfcx7 VZnm51Hs60UGJd1LsmRL6PTjuwW9sYJmtibCCDLyb2aOxZykOAxhACzz14lQITjRRkyE2hrgeQY bqQPuEYYT+l51Y5aUP7mKIb5b+7K53tb4rGoIhkXgqDIqRks/Ybqlbjof+NTD36GPL7HRpQ66RA LGnJ/OO1Utw== X-Google-Smtp-Source: AGHT+IEVcav5Z0xvJoyPUjcMOltjbT1a5u4AXTU7I7eK8zixu/WTlnpUSawY6o+fxWP6DEZTDfPSzg== X-Received: by 2002:a17:907:3e0a:b0:ade:3bec:ea30 with SMTP id a640c23a62f3a-ade3becfeefmr868605166b.1.1749462715332; Mon, 09 Jun 2025 02:51:55 -0700 (PDT) Received: from smtpclient.apple ([89.249.45.14]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ade1dc7b55bsm523545366b.160.2025.06.09.02.51.54 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 09 Jun 2025 02:51:54 -0700 (PDT) Message-ID: <6855DE4C-1CF3-4B89-AC39-114D4C4D0AF8@gmail.com> Content-Type: multipart/alternative; boundary="Apple-Mail=_24905236-BB13-4BCC-BCD5-999715734A2F" Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.600.51.1.1\)) Subject: Re: [PHP-DEV] [RFC] Readonly property hooks Date: Mon, 9 Jun 2025 11:51:44 +0200 In-Reply-To: <1e8634d7-ac1a-4025-b4e2-1948aabf5251@app.fastmail.com> Cc: php internals , php@nicksdot.dev To: Larry Garfield References: <1e8634d7-ac1a-4025-b4e2-1948aabf5251@app.fastmail.com> X-Mailer: Apple Mail (2.3826.600.51.1.1) From: claude.pache@gmail.com (Claude Pache) --Apple-Mail=_24905236-BB13-4BCC-BCD5-999715734A2F Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > Le 8 juin 2025 =C3=A0 06:16, Larry Garfield a = =C3=A9crit : >=20 > As Nick has graciously provided an implementation, we would like to = open discussion on this very small RFC to allow `readonly` on backed = properties even if they have a hook defined. >=20 > https://wiki.php.net/rfc/readonly_hooks >=20 > --=20 > Larry Garfield > larry@garfieldtech.com Hi Larry, Nick, Last summer, the question of allowing hooks on readonly has been raised = as part of the RFC =C2=ABProperty hooks improvements=C2=BB, and at that = time I have raised an objection on allowing the get hook on readonly = properties and I have suggested for a better design for the main issue = it was supposed to solve, see https://externals.io/message/124149#124187 = and the following messages. (The RFC itself was trimmed down to the = non-controversial part.) I=E2=80=99ll repeat here both my objection and = my proposal for better design, but more strongly, with the hope that the = message will be received. The purpose of readonly properties is (citing the original RFC, = https://wiki.php.net/rfc/readonly_properties_v2#rationale) to provide = strong immutable guarantee, i.e.: ```php class Test { public readonly string $prop; =20 public function method(Closure $fn) { $prop =3D $this->prop; $fn(); // Any code may run here. $prop2 =3D $this->prop; assert($prop =3D=3D=3D $prop2); // Always holds. } } ``` By allowing a get hook on readonly property, you are effectively = nullifying this invariant. Invariants must be enforced be the engines = (whenever possible; there is an inevitable loophole until the property = is initialised), and not left to the discretion of the user. If a get = hook on readonly property is allowed, a random user will use its = creativity in order to circumvent the intended invariant (recall: = immutability). I say =E2=80=9Ccreativity=E2=80=9D, not =E2=80=9Cdumbness=E2= =80=9D, because you cannot mechanically tell the two apart: ```php class doc { public readonly int page { get =3D> $this->page + $this->offset; } private int $offset =3D 0; public function __construct(int $page) { $this->page =3D $page; } public function foo() { // $this->offset may be adjusted here } } ``` I know that some people won=E2=80=99t see a problem with that code (see = the cited thread above), and this is a strong reason not to allow that: = you cannot trust the user to enforce invariants that they don=E2=80=99t = understand or are not interested in. (The objection above is for the `get` hook`; there is no such issue with = the `set` hook.) Now, here is the suggestion for a better alternative design, that (1) = don=E2=80=99t allow to break the invariant of immutability, (2) solve = the issue of lazy initialisation (which is, I guess, the main purpose of = the `get` hook on readonly), and (3) also works with nullable = properties: Add an additional hook to backed properties, named `init`. When = attempting to read the value of the backing store, if it is = uninitialised, then the init hook is triggered, which is supposed to = initialise it. =E2=80=94Claude --Apple-Mail=_24905236-BB13-4BCC-BCD5-999715734A2F Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8

Le 8 juin 2025 =C3=A0 06:16, Larry Garfield = <larry@garfieldtech.com> a =C3=A9crit :

As Nick has graciously = provided an implementation, we would like to open discussion on this = very small RFC to allow `readonly` on backed properties even if they = have a hook = defined.

https://wiki.php.net/rfc/readonly_hooks

--
=  Larry Garfield
=  larry@garfieldtech.com

Hi Larry, Nick,

Last summer, the = question of allowing hooks on readonly has been raised as part of the = RFC =C2=ABProperty hooks improvements=C2=BB, and at that time I have = raised an objection on allowing the get hook on readonly properties and = I have suggested for a better design for the main issue it was supposed = to solve, see https://externals.io/m= essage/124149#124187 and the following messages. (The RFC = itself was trimmed down to the non-controversial part.) I=E2=80=99ll = repeat here both my objection and my proposal for better design, but = more strongly, with the hope that the message will be = received.

The purpose of readonly properties is = (citing the original RFC, https:/= /wiki.php.net/rfc/readonly_properties_v2#rationale) to provide = strong immutable guarantee, = i.e.:

```php
class Test = {
    public readonly string = $prop;
 
    public function = method(Closure $fn) {
        $prop =3D = $this->prop;
        $fn(); // Any code = may run here.
        $prop2 =3D = $this->prop;
        assert($prop =3D=3D=3D= $prop2); // Always holds.
    = }
}
```

By allowing a = get hook on readonly property, you are effectively nullifying this = invariant. Invariants must be enforced be the engines (whenever = possible; there is an inevitable loophole until the property is = initialised), and not left to the discretion of the user. If a get hook = on readonly property is allowed, a random user will use its creativity = in order to circumvent the intended invariant (recall: immutability). I = say =E2=80=9Ccreativity=E2=80=9D, not =E2=80=9Cdumbness=E2=80=9D, = because you cannot mechanically tell the two = apart:

```php
class doc = {
    public readonly int page {
  =       get =3D> $this->page + = $this->offset;
    }
    = private int $offset =3D 0;
    public function = __construct(int $page) {
        = $this->page =3D $page;
    }
  =   public function foo() {
        // = $this->offset may be adjusted here
    = }
}
```

I know that = some people won=E2=80=99t see a problem with that code (see the cited = thread above), and this is a strong reason not to allow that: you cannot = trust the user to enforce invariants that they don=E2=80=99t understand = or are not interested in.

(The objection above = is for the `get` hook`; there is no such issue with the `set` = hook.)

Now, here is the suggestion for a better = alternative design, that (1) don=E2=80=99t allow to break the invariant = of immutability, (2) solve the issue of lazy initialisation (which is, I = guess, the main purpose of the `get` hook on readonly), and (3) also = works with nullable properties:

Add an = additional hook to backed properties, named `init`. When attempting to = read the value of the backing store, if it is uninitialised, then the = init hook is triggered, which is supposed to initialise = it.

=E2=80=94Claude

= --Apple-Mail=_24905236-BB13-4BCC-BCD5-999715734A2F--