Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:122680 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 ED32B1AD8F6 for ; Mon, 18 Mar 2024 11:36:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1710761791; bh=529jjz1NJkdMrs4J0WcYavCuJdmATZfi0qVE/ErtCQk=; h=Date:Subject:To:References:From:In-Reply-To:From; b=RVPA4JKp7WPELqtomNc2vCtUIyzA8KiySv0OYneHjatRb2Uz0Re/3CzN2cHpUsZJW d5hxsdcoOipw3SKSmbiAvk7t5fmASfaCSqYJBmWzQ7OQYOtlLJy/MvFS41GjAx81Bp x36tISsBFzC1A/xPVw/WCevtdTbDIGkszOsH2P1dcJRbvAPGQeLTBhId7i1b8Ce5Xs 3iE5Up8o/7ssHzETvo5LXy3/tzPz6Janp3KTseTAOtb2UO6AhrpB36AUDF4++Y8wXf kmvqSvlpxCPBG8usvY+QPXhftqrBWe5kIxsEUjWUF8XFzZwLo0Rml8DasPnO8O4Xsm SE0DCeVFWlmHA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 9795D180070 for ; Mon, 18 Mar 2024 11:36:28 +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,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fhigh6-smtp.messagingengine.com (fhigh6-smtp.messagingengine.com [103.168.172.157]) (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, 18 Mar 2024 11:36:28 +0000 (UTC) Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailfhigh.nyi.internal (Postfix) with ESMTP id CF57F1140110 for ; Mon, 18 Mar 2024 07:36:07 -0400 (EDT) Received: from mailfrontend2 ([10.202.2.163]) by compute4.internal (MEProxy); Mon, 18 Mar 2024 07:36:07 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rwec.co.uk; 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=fm2; t=1710761767; x=1710848167; bh=jVvP8CtQS9 6mCsCTdPTCLT/vhcJKrcjNpG0FhOFFc4g=; b=NFIemodXCDJqI6/NAR2Ka9hK5x ixe1m6yvR+qUKfcDvbwmwt0bk9y8fleeSzSlwGvkpRzWdXnL8XMmdi7qAcNtaXje NbK2NaJWMYQIk+Nz6cRm+tmABjslk/RR3k0kyIdTRCdJbAu0wveHrcJkuYMHuO7Q rCHOesVBuAc3hQRaS1WkT2ZWdQ/C4o5SnJmgvnPWt+fBo7orDtQ/WBYALPBtc5we 9zt+rZ/MC9XLj4Sd5AkRFXlK1kpztB+RiRzUby/2c2m21S2+Dkvcm+VNdFV2y8Ix fSm0wyMrgZhsiQWlgwiz6aITF5lsmrgE3ixeU1wd85ouaZn1FMziuzXF6B+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-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm2; t=1710761767; x=1710848167; bh=jVvP8CtQS96mCsCTdPTCLT/vhcJK rcjNpG0FhOFFc4g=; b=aWsPvEH5ugSZs89eDNiCElgmzmbTm3CH8/9lavpgfEop VugzstiFdjHiWxIlmQlGW3NlKfgw0pAm22eb5VsGoyU2HxnElZ2zRuDaYrhsWQf2 YPGkqrp6860I48L16quBJrbwG8RwKY/r7hWYxBOXDg+ecC5O8F2SAmi13DZ+xRO4 uNCtOKKnOR+6mFfQA8581vaEh8nG6yyyGZ26ZjPoWUL924WRVjD9cNAZ1UMmoeQv pHHOa6tXbsEiqGXTHomJxXd+jMn4iGpTWMQvThWPmCkEZ5wtLaOW/8MJzps9i4CX iMdNC5pMB4GE9vfSMa9g5xWaU83SKcpU55dtrpBvsQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrkeejgdeftdcutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecunecujfgurheptgfkffggfgfuvfhfhfgjsegrtderre dtvdejnecuhfhrohhmpedftfhofigrnhcuvfhomhhmihhnshculgfkoffuohfrngdfuceo ihhmshhophdrphhhphesrhifvggtrdgtohdruhhkqeenucggtffrrghtthgvrhhnpeehte elieeigfeuudeiueeiffdvveehudeufeekjeeugffffedtiedtgeettdelteenucevlhhu shhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehimhhsohhprdhphh hpsehrfigvtgdrtghordhukh X-ME-Proxy: Feedback-ID: id5114917:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Mon, 18 Mar 2024 07:36:07 -0400 (EDT) Content-Type: multipart/alternative; boundary="------------xsd2ZODV07fgca0wbANEqk14" Message-ID: <771e196c-274b-428a-a13c-4078a3f5ecfa@rwec.co.uk> Date: Mon, 18 Mar 2024 11:36:04 +0000 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PHP-DEV] [RFC[ Property accessor hooks, take 2 To: internals@lists.php.net 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> <46609F15-DD40-4BD2-A78A-16021C3447C8@rwec.co.uk> Content-Language: en-GB In-Reply-To: From: imsop.php@rwec.co.uk ("Rowan Tommins [IMSoP]") This is a multi-part message in MIME format. --------------xsd2ZODV07fgca0wbANEqk14 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit On 18/03/2024 00:04, Ilija Tovilo wrote: > I realize this is somewhat inconsistent, but I believe it is > reasonable. If you want to expose the underlying property > by-reference, you need to jump through some additional hoops. I disagree with this reasoning, because I foresee plenty of cases where a virtual property is necessary anyway, so doesn't provide any additional hoop to jump through. But there's not much more to say on this point, so I guess we'll leave it there. > Again, it depends on how you think about it. As you have argued, for a > get-only property, the backing value should not be writable without an > explicit `set;` declaration. You can interpret `set;` as an > auto-generated hook, or as a marker that indicates that the backing > value is accessible without a hook. Regardless of which of these views you start with, it still seems intuitive to me that accesses inside the get hook would bypass the normal rules and write to the raw value. Leaving aside the implementation, there are three things that can happen when you write to a property: a) the set hook is called b) the raw property is written to c) an error is thrown Inside the dynamic scope of a hook, the behaviour is always (b), and I don't see any reason for that to change. From anywhere else, backed properties currently try (a) and fall back to (b); virtual properties try (a) and fall back to (c). I do understand that falling back to (b) makes the implementation simpler, and works well with inheritance and some use cases; but falling back to (c) wouldn't necessarily need a "default hook", just a marker of "has hooks". It occurred to me you could implement it in reverse: auto-generate a hook "set => throw new Error;" and then *remove* it if the user opts in to the default set behaviour. That would keep the "write directly" case optimised "for free"; but it would be awkward for inheritance, as you'd have to somehow avoid calling the parent's hook. > The meaning for `set;` is no longer clear. Does it mean that there's a > generated hook that accesses the backing field? Does it mean that the > backing field is accessible without a hook? Or does it mean that it > accesses the parent hook? The truth is, with inheritance there's no > way to look at the property declaration and fully understand what's > going on, unless all hooks must be spelled out for the sake of clarity > (e.g. `get => parent::$prop::get()`). Yes, I think this is probably a good argument against requiring "set;" I think "be careful when inheriting only one hook" will always be a key rule to teach anyway, because it's easy to mess up (e.g. assuming the parent is backed and accessing $this->foo, rather than calling the parent's hook implementation). But adding "set;" into the mix probably just makes it worse. > I seriously doubt accessing the backing value outside of the current > hook is useful. The backing value is an implementation detail. If it > is absolutely needed, `ReflectionProperty::setRawValue()` offers a way > to do it. I understand the desire for a shorter alternative like > `$field`, but it doesn't seem like the majority shares this desire at > this point in time. The example of clearAll() is a real use case, which people will currently achieve with __get and __set (e.g. the Yii ActiveRecord implementation I linked in one of my previous messages). The alternative wouldn't be reflection, it would just be switching to a virtual property with the value stored in a private field. I think that's fine, it's just drawing the line of which use cases backed properties cover: Kotlin covers more use cases than C#; PHP will cover more than Kotlin (methods able to by-pass a hook when called from that hook); but it will draw the line here. > A different syntax like `$this->prop::raw` comes with similar > complexity issues, similar to those previously discussed for > `parent::$prop`/`parent::$prop = 'prop'`. Yeah, I can't even think of a nice syntax for it, let alone a nice implementation. Let's leave it as a thought experiment, no further action needed. :) Regarding asymmetric types: > I can't speak for IDEs or static > analyzers, but I'm not sure what makes this case special. We can ask > some of their maintainers for feedback. In order to reliably tell the user whether "$a->foo = $b->bar;" is a type-safe operation, the analyser will need to track two types for every property, the "gettable type" and the "settable type", and apply them in the correct contexts. I've honestly no idea whether that will be easy or hard; it will probably vary between tools. In particular, I get the impression IDEs / editor plugins sometimes have a base implementation used for multiple programming languages, and PHP might be the only one that needed this extra tracking. Regards, -- Rowan Tommins [IMSoP] --------------xsd2ZODV07fgca0wbANEqk14 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
On 18/03/2024 00:04, Ilija Tovilo wrote:
I realize this is somewhat inconsistent, but I believe it is
reasonable. If you want to expose the underlying property
by-reference, you need to jump through some additional hoops.


I disagree with this reasoning, because I foresee plenty of cases where a virtual property is necessary anyway, so doesn't provide any additional hoop to jump through.

But there's not much more to say on this point, so I guess we'll leave it there.



Again, it depends on how you think about it. As you have argued, for a
get-only property, the backing value should not be writable without an
explicit `set;` declaration. You can interpret `set;` as an
auto-generated hook, or as a marker that indicates that the backing
value is accessible without a hook.


Regardless of which of these views you start with, it still seems intuitive to me that accesses inside the get hook would bypass the normal rules and write to the raw value.

Leaving aside the implementation, there are three things that can happen when you write to a property:

a) the set hook is called
b) the raw property is written to
c) an error is thrown

Inside the dynamic scope of a hook, the behaviour is always (b), and I don't see any reason for that to change. From anywhere else, backed properties currently try (a) and fall back to (b); virtual properties try (a) and fall back to (c).

I do understand that falling back to (b) makes the implementation simpler, and works well with inheritance and some use cases; but falling back to (c) wouldn't necessarily need a "default hook", just a marker of "has hooks".

It occurred to me you could implement it in reverse: auto-generate a hook "set => throw new Error;" and then *remove* it if the user opts in to the default set behaviour. That would keep the "write directly" case optimised "for free"; but it would be awkward for inheritance, as you'd have to somehow avoid calling the parent's hook.



The meaning for `set;` is no longer clear. Does it mean that there's a
generated hook that accesses the backing field? Does it mean that the
backing field is accessible without a hook? Or does it mean that it
accesses the parent hook? The truth is, with inheritance there's no
way to look at the property declaration and fully understand what's
going on, unless all hooks must be spelled out for the sake of clarity
(e.g. `get => parent::$prop::get()`).


Yes, I think this is probably a good argument against requiring "set;"

I think "be careful when inheriting only one hook" will always be a key rule to teach anyway, because it's easy to mess up (e.g. assuming the parent is backed and accessing $this->foo, rather than calling the parent's hook implementation). But adding "set;" into the mix probably just makes it worse.



I seriously doubt accessing the backing value outside of the current
hook is useful. The backing value is an implementation detail. If it
is absolutely needed, `ReflectionProperty::setRawValue()` offers a way
to do it. I understand the desire for a shorter alternative like
`$field`, but it doesn't seem like the majority shares this desire at
this point in time.


The example of clearAll() is a real use case, which people will currently achieve with __get and __set (e.g. the Yii ActiveRecord implementation I linked in one of my previous messages). 

The alternative wouldn't be reflection, it would just be switching to a virtual property with the value stored in a private field. I think that's fine, it's just drawing the line of which use cases backed properties cover: Kotlin covers more use cases than C#; PHP will cover more than Kotlin (methods able to by-pass a hook when called from that hook); but it will draw the line here.



A different syntax like `$this->prop::raw` comes with similar
complexity issues, similar to those previously discussed for
`parent::$prop`/`parent::$prop = 'prop'`.


Yeah, I can't even think of a nice syntax for it, let alone a nice implementation. Let's leave it as a thought experiment, no further action needed. :)


Regarding asymmetric types:

I can't speak for IDEs or static
analyzers, but I'm not sure what makes this case special. We can ask
some of their maintainers for feedback.


In order to reliably tell the user whether "$a->foo = $b->bar;" is a type-safe operation, the analyser will need to track two types for every property, the "gettable type" and the "settable type", and apply them in the correct contexts.

I've honestly no idea whether that will be easy or hard; it will probably vary between tools. In particular, I get the impression IDEs / editor plugins sometimes have a base implementation used for multiple programming languages, and PHP might be the only one that needed this extra tracking.


Regards,

-- 
Rowan Tommins
[IMSoP]
--------------xsd2ZODV07fgca0wbANEqk14--