Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:122650 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 qa.php.net (Postfix) with ESMTPS id DD9481AD8F6 for ; Sat, 16 Mar 2024 00:20:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1710548440; bh=fM5vuEdGpwKFG+DqRKTMWHoDWuj1Lwc2bNSz31tWwdA=; h=In-Reply-To:References:Date:From:To:Subject:From; b=JVyNZ27qNzC4NDKTpcemhAfFk0LZ+emR1z3vyiD4O58ILPjnIpBJRf5Y1o82U6SFO 1F+W0aPRTpd6Z/jKCgJbFLiJ5wuvmflFMdSrnnpMXq7BnMIhvbNaHiKVPWn2l01dpN Awr6HUGHjJG3/RWb9XZhaLokYPQoS6spoBSN2TMXnW6kkxtp0vzhUDrZ89gzcYAVYg vd6k2oVR7/qAYTHt0fqweu8NfVUUlSvlJ3Yw97QUhC0sNxz9PrwDP/ty+53otuLJGM /grh7R2g08QHXM+jV8J8sF+4JPNyqNt3pECoYLT3OEyxW73Fmpfh4iI7ZJwx6RuHZw pHCWew6YA6DNQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 795C2180698 for ; Sat, 16 Mar 2024 00:20:39 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,RCVD_IN_DNSWL_LOW, SPF_HELO_PASS,SPF_NONE,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from wout1-smtp.messagingengine.com (wout1-smtp.messagingengine.com [64.147.123.24]) (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 ; Sat, 16 Mar 2024 00:20:38 +0000 (UTC) Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailout.west.internal (Postfix) with ESMTP id 0B61A32001FF for ; Fri, 15 Mar 2024 20:20:18 -0400 (EDT) Received: from imap50 ([10.202.2.100]) by compute1.internal (MEProxy); Fri, 15 Mar 2024 20:20:19 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; 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=fm1; t=1710548418; x= 1710634818; bh=ED+rcfN/4RlFvf6Duo5Xr3bIsBoeO6secSHjQ9CThbU=; b=a tzHR2Gc3yvsrMsIE2/1SZ0Bd8wnIKcMp5SkDkfo5cKuyjt2R00DMfMC3IU5X/GSq E/m0go8Ye8N9VwmQSXRg1hQhxM5bpo41yCRH0ExFCQwm8e8+M2zadsdBcPSRtY6t x8OoloklhQzk2cYhW6PpjM1580VAqSHMGidzdjstrlO6WzjeAv8AC7wWozbiBPNt XkkT0ergDJ0kUxQB5PoC2COo4lomvnhYnQSlf0E8QmXJJTPthnxj0OW9spTEwrx7 hQIhzZ7EwkItS8Fhzcs34ZFgkSDQ6P8eB7WzoIL8hibI+1XoNGMC34ahsKmKlFTg 2I+VfoDLWOxTVUohe5kDw== 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-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm1; t=1710548418; x=1710634818; bh=ED+rcfN/4RlFvf6Duo5Xr3bIsBoe O6secSHjQ9CThbU=; b=egfvVi7FRBtFL3q5MPTJ95k7KvNIKsqHkGAxcC0W8Lmf 8cF/P3a46ezibLWq+bi1r6fzHNqXMpIIsWJ5pdx2HnsUp/NBwtc7e0pgP3BKeRGq dkjzPIGbbMVzMsAy3iPKExlViTMDHxP3aC8fvqgMKyBRSoI4PNSmM2MtTHJwM49U 34HzjNrZPdnlVxZJ584Q4aKWCsPBhoggHp8WrVKzUbkNLoRhU7K2m1b0Ewlh5HqP rMftu/oPxu41C1lq70zyabjR7kZ3ffg4L0h/ukt2MQZGzf7b7rvRembtCBFcgiP6 lbyPmAloqdZan0l+wxILjuHCPg4Mfqqjj25kHb74Iw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrkedtgddulecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenuc fjughrpefofgggkfgjfhffhffvufgtsehttdertderredtnecuhfhrohhmpedfnfgrrhhr hicuifgrrhhfihgvlhgufdcuoehlrghrrhihsehgrghrfhhivghlughtvggthhdrtghomh eqnecuggftrfgrthhtvghrnhepgeelgfekudeivddvteffueejffdthfejieevhefgffek udevkedtvdelvddvffefnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrg hilhhfrhhomheplhgrrhhrhiesghgrrhhfihgvlhguthgvtghhrdgtohhm X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 1829C1700093; Fri, 15 Mar 2024 20:20:18 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.11.0-alpha0-300-gdee1775a43-fm-20240315.001-gdee1775a Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 Message-ID: In-Reply-To: References: <7eada0fd-39c5-4a89-8c74-80c671801a2d@app.fastmail.com> <1698692e-8eb1-4bfc-a743-375696cd8f1c@rwec.co.uk> <154481e0-5f62-4026-994a-28a644d71527@app.fastmail.com> Date: Sat, 16 Mar 2024 00:19:57 +0000 To: "php internals" Subject: Re: [PHP-DEV] [RFC[ Property accessor hooks, take 2 Content-Type: text/plain From: larry@garfieldtech.com ("Larry Garfield") On Fri, Mar 15, 2024, at 11:25 PM, Rowan Tommins [IMSoP] wrote: > On 15 March 2024 17:11:29 GMT, Larry Garfield wrote: >>On Wed, Mar 13, 2024, at 10:26 PM, Rowan Tommins [IMSoP] wrote: >>> I think it would be more helpful to justify this design on its own >>> merits, particularly because it's a significant difference from other >>> languages (which either don't have a "real property" behind the hooks, >>> or in Kotlin's case allow access to it only *directly* inside the hook >>> definitions, via the "field" keyword). >> >>I'm not sure I follow. The behavior we have currently is very close to how Kotlin works, from a user perspective. > > > Unless I'm misunderstanding something, the backing field in Kotlin is > accessible only inside the hooks, nowhere else. I don't know what would > happen if a hook caused a recursive call to itself, but there's no > mention in the docs of it bypassing the hooks, only this: > >> This backing field can be referenced in the accessors using the `field` identifier > > and > >> The `field` identifier can only be used in the accessors of the property. > > And then a section explaining that more complex hooks should use a > separate backing property - which is the only option in C#, and roughly > what people would do in PHP today with __get and __set. > > Kotlin does have a special syntax for "delegating" hooks, but looking > at the examples, they do not use the backing field at all, they have to > provide their own storage. > > > >>I've lost track of which specific issue you have an issue with or would want changed. The guards to prevent an infinite loop are necessary, for the same reasons as they are necessary for __get/__set. > > I understand that *something* needs to happen if a recursive call > happens, but it could just be an error, like any other unbounded > recursion. > > I can also understand the temptation to make it something more useful > than an error, and provide a way to access the "backing field" / "raw > value" from outside the hook. But it does lead to something quite > surprising: the same line of code does different things depending on > how it is called. > > I doubt many people have ever discovered that __get and __set work that > way, since as far as I can see it's only possible to use deliberately > if you're dynamically adding and unsetting properties inside your class. > > So, I don't necessarily think hooks working that way is the wrong > decision, I just think it's a decision we should make consciously, not > one that's obvious. Well, reading/writing from within a set/get hook is an obvious use case to support. We cannot do cached properties easily otherwise: public string $expensive { get => $this->expensive ??= $this->compute(); set { if (strlen($value) < 50) throw new Exception(); $this->expensive = $value; } } So disabling the hooks from within the hooks seems like the only logical solution there. (Short of bringing back $field and making it mandatory, which is actually much harder than it sounds because of the ++ et al operators that would need to be supported.) The other case then becomes: class Foo { public string $a { get => $this->expensive ??= $this->compute(); set { if (strlen($value) < 50) throw new Exception(); $this->expensive = $value; } } public function compute() { $start = $this->expensive ?? 'a'; return $start . 'b'; } } Inside compute(), the logic requires reading from $this->expensive. If we have no guard, that would cause an infinite loop. If the guard extends down the call stack, then the loop is eliminated. That does mean, as you note, that if you call $foo->compute(), the first call to $this->expensive will invoke the get hook, but subsequent calls to it will not. That may seem odd, but in practice, we do not see any other alternative that doesn't make infinite loops very easy to write. This is, of course, a highly contrived example. In practice, I don't expect it to come up much in the first place. It was this logic that led us to the current implementation: Once you're within the callstack of a hook, you bypass that property's hooks. We then noted that __get/__set have essentially the same guard logic. It doesn't come up often because, again as you note, it's only relevant in even more contrived examples. So yes, the current logic is very deliberate and based on a process of elimination to arrive at what we feel is the only logical design. Likely the same process of elimination that led to the behavior of the guards on __get/__set, which is why they are essentially the same. Being the same also makes the language more predictable, which is also a design goal for this RFC. (Hence why "this is the same logic as methods/__get/other very similar thing" is mentioned several times in the RFC. Consistency in expectations is generally a good thing.) In theory we could also forbid accessing a property within the call stack of its hooks; in that case, the above code would simply error. However, that is a less-optimal solution because then compute() is only sometimes callable, depending on the callstack. That is no less weird than $this->expensive skipping hooks only sometimes. It would also be inconsistent with how __get/__set work, which makes hooks less capable and less consistent. That's why we don't think that's a good way of doing it. Also, we've rewritten the references and arrays sections in the RFC. Nothing actually changed in the implementation, but it should be a lot clearer now that only a fairly small subset of impossible situations are blocked. Most things "just work," and when they don't, they (again) don't for a logical reason that parallels the behavior of the language elsewhere. There's even some nice summary tables. :-) --Larry Garfield