Re: [RFC[ Property accessor hooks, take 2
| From: | Rowan Tommins [IMSoP] | Date: | Mon, 18 Mar 2024 11:36:04 +0000 |
| Subject: | Re: [RFC[ Property accessor hooks, take 2 | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-122680@lists.php.net to get a copy of this message | ||
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 explicitRegardless 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.set;declaration. You can interpretset;as an auto-generated hook, or as a marker that indicates that the backing value is accessible without a hook.
The meaning forYes, 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.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()).
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,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.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.
A different syntax likeYeah, 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:$this->prop::rawcomes with similar complexity issues, similar to those previously discussed forparent::$prop/parent::$prop = 'prop'.
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]