Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127998 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 DC4411A00BC for ; Thu, 10 Jul 2025 18:44:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1752172932; bh=1lJhQadT9lmOyyQ3tDsCf1VwgNUeusx07eofyxdnC5U=; h=Date:From:To:In-Reply-To:References:Subject:From; b=WgARJuhoxfipLVeYXN9cYwVPGggxB8Nc5IyycsOFhr1yDJkPHCuhAk6nXbCMBvftC mVjYYdyMz8N6aoBbalZm6od4w/xnQGqCcpBSCSWMOe1BXBqQSwUfGnkidnz8DGYGg0 qtV7cyAWMhKZOHm1g3ByvB+983+lEb8GpH8z/DsoUuurHnlNjUVVGvf17gdZlKMZ9z IgT9QsZyJCjoedTOqTagVovBrfcLi7FbX7kboiYv8Ih94q58zk3X72DlVMq6RsDd8T PA003ny9x9uuLHy07Cgw+1KTK0DIaZWEhUrvwaOcAgwiR/rTH4eQjOFJILpBXPy9Ex PkMNSxAk4PlBw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id DC341180068 for ; Thu, 10 Jul 2025 18:42:11 +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=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,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 fhigh-b3-smtp.messagingengine.com (fhigh-b3-smtp.messagingengine.com [202.12.124.154]) (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 ; Thu, 10 Jul 2025 18:42:11 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailfhigh.stl.internal (Postfix) with ESMTP id C36517A0154 for ; Thu, 10 Jul 2025 14:44:00 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Thu, 10 Jul 2025 14:44:00 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1752173040; x=1752259440; bh=t8uWaaJmzw LRdVye+b10NZDEPdZTJAxJk2s6eYszAeM=; b=c439Csg5TUHhusj5uhwBH/L90P ZZQl3loDBaLfklTYSEy27a0nHQpB76Tawcu0bLM+8MkDhqjjAHaVIlkg/3AfZ0Lq F7t9d2iqMP84BQHDmjLzY46zEKLb7PdWRNSo8rVyyPhq462sXV9ann6NTJ3NexVj +eTS85kVcB2SH2JBOy5I56vWo6hveCS5+A+G6n1f+KO5Hvw7bpcCRlZ4NfxIHACS kbtEtmfygUZlvA+D0KfUquCcVWyxuZnLgyhLxLTzYHxGOpUTRuAKQpBrb/P0e4UD 5G6ulEbpmVKvaXtv/L8fy49/WuWyGBQy7e1XN4crnwgOFmDSsZ81KRauVt+g== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t= 1752173040; x=1752259440; bh=t8uWaaJmzwLRdVye+b10NZDEPdZTJAxJk2s 6eYszAeM=; b=daCa12KA1jGbY7jX9FMLmeRdVESxkxaHkH9bBOydabhLHyLkf3S HyCMqe+bq2gEbQF0dXAnYSmVNXBvmEGekkKrnoBZ2Dcvz38d+0KTmuIKlb49jzKj 8dz8LQUBblJRTQZZ7ubOaVXmJcclth/QRDRUtv5gHpZyxnEznGH0ZvWbF5b+bpot NyeDKRX8VBV6XBR2VbWfhwiQ2hZ2qC3otZKNpON5hTyV/xC6kvrURxUWSsdz65Dz JFez288Csyejtk/ZKCwO0av5y94iKQEgkavzA+1rxQ4BS2ng+Jx1bZpOIhAxPAiI Ajn0GJDiSCiHvbFb8Iz9YQ8iCTzm2gDbvuA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdefgdeguddulecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecunecujfgurhepofggfffhvffkjghfufgtsegrtderreertd ejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdfuceorhhosgessghothhtlhgvugdr tghouggvsheqnecuggftrfgrthhtvghrnheptdeujedttefhueelhfdtleeiudetlefftd duleehffegtdeihefhleeijefgveegnecuvehluhhsthgvrhfuihiivgeptdenucfrrghr rghmpehmrghilhhfrhhomheprhhosgessghothhtlhgvugdrtghouggvshdpnhgspghrtg hpthhtohepuddpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepihhnthgvrhhnrghl sheslhhishhtshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 28CD91820073; Thu, 10 Jul 2025 14:43:59 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: T5f4527d1a0d4de24 Date: Thu, 10 Jul 2025 20:43:38 +0200 To: internals@lists.php.net Message-ID: In-Reply-To: References: <1e8634d7-ac1a-4025-b4e2-1948aabf5251@app.fastmail.com> <6acab95a554fe5e188364840ea36d2b7@bastelstu.be> Subject: Re: [PHP-DEV] [RFC] Readonly property hooks Content-Type: multipart/alternative; boundary=8f7c50a40a6944edbdadb6fbb73b48c4 From: rob@bottled.codes ("Rob Landers") --8f7c50a40a6944edbdadb6fbb73b48c4 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Thu, Jul 10, 2025, at 17:34, Larry Garfield wrote: > On Thu, Jul 10, 2025, at 5:43 AM, Tim D=C3=BCsterhus wrote: > > Hi > > > > Am 2025-07-08 17:32, schrieb Nicolas Grekas: > >> I also read Tim's argument that new features could be stricter. If = one > >> wants to be stricter and forbid extra behaviors that could be added= by > >> either the proposed hooks or __get, then the answer is : make the c= lass > >> final. This is the only real way to enforce readonly-ness in PHP. > > > > Making the class final still would not allow to optimize based on th= e=20 > > fact that the identity of a value stored in a readonly property will= not=20 > > change after successfully reading from the property once. Whether or= not=20 > > a property hooked must be considered an implementation detail, since= a=20 > > main point of the property hooks RFC was that hooks can be added and=20 > > removed without breaking compatibility for the user of the API. > > > >> engine-assisted strictness in this case. You cannot write such code= in=20 > >> a > >> non-readonly way by mistake, so it has to be by intent. > > > > That is saying "it's impossible to introduce bugs". > > > >> PS: as I keep repeating, readonly doesn't immutable at all. I know = this=20 > >> is > >> written as such in the original RFC, but the concrete definition and > >> implementation of readonly isn't: you can set mutable objects to=20 > >> readonly > >> properties, and that means even readonly classes/properties are=20 > >> mutable, in > >> the generic case. > > > > `readonly` guarantees the immutability of identity. While you can=20 > > certainly mutate mutable objects, the identity of the stored object=20 > > doesn't change. > > > > Best regards > > Tim D=C3=BCsterhus >=20 > Nick previously suggested having the get-hook's first return value cac= hed; it would still be subsequently called, so any side effects would st= ill happen (though I don't know why you'd want side effects), but only t= he first returned value would ever get returned. Would anyone find that= acceptable? (In the typical case, it would be the same as the current = $this->foo ??=3D compute() pattern, just with an extra cache entry.) >=20 > --Larry Garfield >=20 I think that only covers one use-case for getters on readonly classes. T= ake this example for discussion: readonly class User { public int $elapsedTimeSinceCreation { get =3D> time() - $this->crea= tedAt; } private int $cachedResult; public int $totalBalance { get =3D> $this->cachedResult ??=3D 5+10; } public int $accessLevel { get =3D> getCurrentAccessLevel(); } public function __construct(public int $createdAt) {} } $user =3D new User(time() - 5); var_dump($user->elapsedTimeSinceCreation); // 5 var_dump($user->totalBalance); // 15 var_dump($user->accessLevel); // 42 In this example, we have three of the most common ones: 1. Computed Properties (elapsedTimeSinceCreation): these are properties= of the object that are relevant to the object in question, but are not = static. In this case, you are not writing to the object. It is still "re= adonly". 2. Memoization (expensiveCalculation): only calculate the property once= and only once. This is a performance optimization. It is still "readonl= y". 3. External State (accessLevel): properties of the object that rely on = some external state, which due to architecture or other convienence may = not make sense as part of object construction. It is still "readonly". You can mix-and-match these to provide your own level of immutability, b= ut memoization is certainly not the only one.=20 You could make the argument that these should be functions, but I'd posi= t that these are properties of the user object. In other words, a functi= on to get these values would probably be named `getElapsedTimeSinceCreat= ion()`, `getTotalBalance`, or `getAccessLevel` -- we'd be writing getter= s anyway. =E2=80=94 Rob --8f7c50a40a6944edbdadb6fbb73b48c4 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


On Thu, Jul 10, 2025, at 17:34, Larry Garfield wrote:<= /div>
On Thu, Jul 10,= 2025, at 5:43 AM, Tim D=C3=BCsterhus wrote:
> Hi
>
> Am 2025-07-08 17:32, schrieb Nicolas Grekas:
<= div>>> I also read Tim's argument that new features could be stric= ter. If one
>> wants to be stricter and forbid extra beh= aviors that could be added by
>> either the proposed hoo= ks or __get, then the answer is : make the class
>> fina= l. This is the only real way to enforce readonly-ness in PHP.
= >
> Making the class final still would not allow to opti= mize based on the 
> fact that the identity of a value= stored in a readonly property will not 
> change afte= r successfully reading from the property once. Whether or not 
> a property hooked must be considered an implementation detail= , since a 
> main point of the property hooks RFC was = that hooks can be added and 
> removed without breakin= g compatibility for the user of the API.
>
>&g= t; engine-assisted strictness in this case. You cannot write such code i= n 
>> a
>> non-readonly way by mist= ake, so it has to be by intent.
>
> That is sa= ying "it's impossible to introduce bugs".
>
>&= gt; PS: as I keep repeating, readonly doesn't immutable at all. I know t= his 
>> is
>> written as such in th= e original RFC, but the concrete definition and
>> imple= mentation of readonly isn't: you can set mutable objects to 
<= div>>> readonly
>> properties, and that means even= readonly classes/properties are 
>> mutable, in
>> the generic case.
>
> `readon= ly` guarantees the immutability of identity. While you can 
> certainly mutate mutable objects, the identity of the stored obj= ect 
> doesn't change.
>
> B= est regards
> Tim D=C3=BCsterhus

N= ick previously suggested having the get-hook's first return value cached= ; it would still be subsequently called, so any side effects would still= happen (though I don't know why you'd want side effects), but only the = first returned value would ever get returned.  Would anyone find th= at acceptable?  (In the typical case, it would be the same as the c= urrent $this->foo ??=3D compute() pattern, just with an extra cache e= ntry.)

--Larry Garfield


I think that only covers one use-case for = getters on readonly classes. Take this example for discussion:

readonly class User {
    publi= c int $elapsedTimeSinceCreation { get =3D> time() - $this->created= At; }
    private int $cachedResult;
=     public int $totalBalance { get =3D> $this->cach= edResult ??=3D 5+10; }
    public int $accessLe= vel { get =3D> getCurrentAccessLevel(); }
   = ; public function __construct(public int $createdAt) {}
}

$user =3D new User(time() - 5);
var_dump(= $user->elapsedTimeSinceCreation); // 5
var_dump($user->t= otalBalance); // 15
var_dump($user->accessLevel); // 42

In this example, we have three of the most common = ones:
  1. Computed Properties (elapsedTimeSinceCreation): t= hese are properties of the object that are relevant to the object in que= stion, but are not static. In this case, you are not writing to the obje= ct. It is still "readonly".
  2. Memoization (expensiveCalculation): = only calculate the property once and only once. This is a performance op= timization. It is still "readonly".
  3. External State (accessLevel)= : properties of the object that rely on some external state, which due t= o architecture or other convienence may not make sense as part of object= construction. It is still "readonly".
You can mix-and-mat= ch these to provide your own level of immutability, but memoization is c= ertainly not the only one. 

You could make= the argument that these should be functions, but I'd posit that these a= re properties of the user object. In other words, a function to get thes= e values would probably be named `getElapsedTimeSinceCreation()`, `getTo= talBalance`, or `getAccessLevel` -- we'd be writing getters anyway.

=E2=80=94 Rob
--8f7c50a40a6944edbdadb6fbb73b48c4--