Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128038 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 9A4BC1A00BC for ; Mon, 14 Jul 2025 13:38:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1752500226; bh=PpJvEtyB9je+cqNzIhmgjnqGxK7PRr0R3E6uWhwRqbI=; h=Date:From:To:In-Reply-To:References:Subject:From; b=hR/mvuJ7f5LHgrfvfJJwgBEKiFXivIolT+nFkVCEdGw1wniHuKOCrfrPF6pMYwsYr +ORhxQ8kyRk4GhPDX7ilM/Q+IBNWD7mxcr5BJZWScxA8VgecxkZjI2UptXNKb+3fGH /UgM3H8HaznLRbnpk5tRG3PnAgbNM/E/kU0+P/xRvwjuzP8L8ZIzjpJwXr9kY33SYf jxuND7c5+/WZ2sIaDoGYLi+1oi4DMXAgUKgIK+mZBZVYOCtKchQnsUK4ZAKYjhei// nQA9+ZZyo9PJ5lj5I/O+PCu+uWl/5Ax2R9lwn8KIJqxgD5jQ0ldMd2BHQXa6Rw97uA /7upFAuoPJFkw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 360E3180079 for ; Mon, 14 Jul 2025 13:37:05 +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,RCVD_IN_DNSWL_LOW, SPF_HELO_PASS,SPF_NONE 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-b2-smtp.messagingengine.com (fhigh-b2-smtp.messagingengine.com [202.12.124.153]) (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, 14 Jul 2025 13:37:05 +0000 (UTC) Received: from phl-compute-10.internal (phl-compute-10.phl.internal [10.202.2.50]) by mailfhigh.stl.internal (Postfix) with ESMTP id F3B977A02E1 for ; Mon, 14 Jul 2025 09:38:52 -0400 (EDT) Received: from phl-imap-02 ([10.202.2.81]) by phl-compute-10.internal (MEProxy); Mon, 14 Jul 2025 09:38:53 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-transfer-encoding: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=fm2; t=1752500332; x=1752586732; bh=ESf0KOELIaoZvB4YwcBDs FX3aILa43fSb1Rl0Q8y3YM=; b=O44rOoSlOr7VQrxQroe0i/6Cy848DvMsSGCWn il5SvHX6G2QP3n272UhHUSaykUbTb7D1dNvgOlmx8fs2oDxu1ymkyIndz0KkrI8Y OqwDOh//7rzqUv02B0DE3KUnk0xCQTyCxy2c4jjHqaI6z8J1Wbh1WFFJ+oCVJJDj 7dMAwFF2JE/pkJ61EjyH2HWuh+treclCCO1tkc4DI0F4taYxVev4gEPRTsG+pDJm n4GjWgZoXW0TGAwSvZTs7w4IJCBnmLfKFh6O9pAx6rFYG1MPmOcpvTl518uOFvlT xAhISbF6bnN0ahLF5W5nCJR7bQsDB+keIUDckvIGtTkxh5A0g== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding: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=1752500332; x=1752586732; bh=E Sf0KOELIaoZvB4YwcBDsFX3aILa43fSb1Rl0Q8y3YM=; b=kq6jlsz90oYkio4lm vdZGU08msvV1tagInHD1TkfmJNTbdhIAfP2iWVFtAKGmSYjRFhmfbZy4SkMqHxY0 z/XHMq8OVwTIyRdILIL79g5j8fEfc8btZLvlWBZFVU9uMtoNh0VHu40gvv0wlXSc 2ssjSgDXBLa1WYL1R5jJ1RC4vi0hlwwAZpK6M5dSkXZD2+BRDNqrf2kDvYYVrOg/ T9iJ8gRwYG7Ue4KGrJX2AZ1a7pYNSNv6eDvq8OGeY/nuihNXrmNFzRwu3fJiI4dT RCezEunUF9jhERKq93YaPCoNgw/sQaO/7SzGuZrtJXkWKqvvzzFNvUB1K5OT54SJ Go8Pg== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdefgdehvddtlecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjug hrpefoggffhffvkfgjfhfutgfgsehtqhertdertdejnecuhfhrohhmpedfnfgrrhhrhicu ifgrrhhfihgvlhgufdcuoehlrghrrhihsehgrghrfhhivghlughtvggthhdrtghomheqne cuggftrfgrthhtvghrnhepleduvdduleekieejkeeukefgvdeifeelveevjeefhfeftdeu gfduieeuheffleeknecuffhomhgrihhnpehphhhprdhnvghtpdhgihhthhhusgdrtghomh enucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehlrghr rhihsehgrghrfhhivghlughtvggthhdrtghomhdpnhgspghrtghpthhtohepuddpmhhoug gvpehsmhhtphhouhhtpdhrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrphhh phdrnhgvth X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 7BD4B700065; Mon, 14 Jul 2025 09:38:52 -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: Taa1a9d92243b6726 Date: Mon, 14 Jul 2025 08:38:32 -0500 To: "php internals" Message-ID: <0856c89f-2000-448a-bbbf-c145a8699f6a@app.fastmail.com> In-Reply-To: References: <1e8634d7-ac1a-4025-b4e2-1948aabf5251@app.fastmail.com> <9D5043B2-1589-4FD5-B289-6E98FB1177BE@nicksdot.dev> Subject: Re: [PHP-DEV] [RFC] Readonly property hooks Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable From: larry@garfieldtech.com ("Larry Garfield") On Sun, Jul 13, 2025, at 6:28 PM, Ilija Tovilo wrote: > Hi Nick > > On Fri, Jul 11, 2025 at 6:31=E2=80=AFAM Nick wrote: >> >>> On 8. Jun 2025, at 11:16, Larry Garfield wr= ote: >>> >>> https://wiki.php.net/rfc/readonly_hooks >>> >>> To not get this buried in individual answers to others: >> >> I came up with two alternative implementations which cache the comput= ed `get` hook value. >> One leverages separate cache properties, the other writes directly to= the backing store. >> >> Links to the alternative branches can be found in the description of = the original PR. >> https://github.com/php/php-src/pull/18757 > > I am not a fan of the caching approach. The implementation draft for > this approach [^1] works by storing the assigned value in the property > slot, and replacing it with the value returned from get one called for > the first time. One of the issues here is that the backing value is > observable without calling get. For example: > > ``` > class C { > public public(set) readonly string $prop { > get =3D> strtoupper($this->prop); > } > } > $c =3D new C(); > $c->prop =3D 'foo'; > var_dump(((array)$c)['prop']); // foo > $c->prop; > var_dump(((array)$c)['prop']); // FOO > ``` > > Here we can see that the underlying value changes, despite the > readonly declaration. This is especially problematic for things like > [un]serialize(), where calling serialize() before or after accessing > the property will change which underlying value is serialized. Even > worse, we don't actually know whether an unserialized property has > already called the get hook. > > ``` > class C { > public public(set) readonly int $prop { > get =3D> $this->prop + 1; > } > } > $c =3D new C(); > $c->prop =3D 1; > $s1 =3D serialize($c); > $c->prop; > $s2 =3D serialize($c); > var_dump(unserialize($s1)->prop); // int(2) > var_dump(unserialize($s2)->prop); // int(3) > ``` > > Currently, get is always called after unserialize(). There may be > similar issues for __clone(). > > For readable and writable properties, the straight-forward solution is > to move the logic to set. > > ``` > class C { > public public(set) readonly int $prop { > set =3D> $value + 1; > } > } > ``` > > This is slightly differently, semantically, in that it executes any > potential side-effects on write rather than read, which seems > reasonable. This also avoids the implicit mutation mentioned > previously. At least in these cases, disallowing readonly + get seems > reasonable to me. I will say that this doesn't solve all get+set > cases. For example, proxies. Hopefully, lazy objects can mostly bridge > this gap. > > Another case is lazy getters. > > ``` > class C { > public readonly int $magicNumber { > get =3D> expensiveComputation(); > } > } > ``` > > This does not seem to work in the current implementation: > >> Fatal error: Hooked virtual properties cannot be declared readonly > > I presume it would be possible to fix this, e.g. by using readonly as > a marker to add a backing value to the property. I'm personally not > too fond of making the rules on which properties are backed more > complicated, as this is already a common cause for confusion. I also > fundamentally don't like that readonly changes whether get is called. > Currently, if hooks are present, they are called. This adds more > special cases to an already complex feature. > > To me it seems the primary motivator for this RFC are readonly > classes, i.e. to prevent the addition of hooks from breaking readonly > classes. However, as lazy-getters are de-facto read-only, given they > are only writable from the extremely narrow scope of the hook itself, > the modifier doesn't do much. Maybe an easier solution would be to > provide an opt-out of readonly. Thanks, Ilija. You expressed my concerns as well. And yes, in practice= , readonly classes over-reaching is the main use case; if you're marking= individual properties readonly, then just don't mark the one that has a= hook on it (use aviz if needed) and there's no issue. =20 Perhaps we're thinking about this the wrong way, though? So far we've t= alked as though readonly makes the property write-once. But... what if = we think of it as applying to the field, aka the backing value? So readonly doesn't limit calling the get hook, or even the set hook, mu= ltiple times. Only writing to the actual value in the object table. Th= at gives the exact same set of guarantees that a getX()/setX() method wo= uld give. The methods can be called any number of times, but the stored= value can only be written once. That would allow conditional set hooks, conditional gets, caching gets (= like we already have with ??=3D), and so on. The mental model is simple= and easy to explain/document. The behavior is the same as with methods= . But the identity of the stored value would be consistent. It would not guarantee $foo->bar =3D=3D=3D $foo->bar in all cases (thoug= h that would likely hold in the 99% case in practice), but then, $foo->g= etBar() =3D=3D=3D $foo->getBar() has never been guaranteed either. Would that way of looking at it be acceptable to folks? --Larry Garfield