Re: [RFC] Readonly property hooks
| From: | Nicolas Grekas | Date: | Wed, 09 Jul 2025 11:39:35 +0000 |
| Subject: | Re: [RFC] Readonly property hooks | ||
| References: | 1 2 3 4 5 6 7 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-127967@lists.php.net to get a copy of this message | ||
Thanks for your detailed thoughts, Claude. I'd like to offer my perspective
on some of the points you raised.
Le mer. 9 juil. 2025 à 12:53, Claude Pache <claude.pache@gmail.com> a
écrit :
>
>
> Le 8 juil. 2025 à 17:32, Nicolas Grekas <nicolas.grekas+php@gmail.com> a
> écrit :
>
> I read Claude's concern, and I agree with Larry's response: the engine
> already allows readonly to be bypassed using __get. The added hook doesn't
> make anything more lenient.
>
>
> It is true that readonly could be bypassed by __get(); but this is a
> legacy behaviour, and you have to take an explicit step to make it
> possible. For those unaware of the awful hack, here is a minimal test case:
>
> https://3v4l.org/N78An
>
> where the
unset(...) is mandatory to make it “work”.
>
> Are we obligated to sanction shortcomings of legacy concepts in newly
> introduced concepts that are supposed to replace them? Or can we do
> something better? I’ve outlined in a previous email what I think is a
> better design for such situation (namely an init hook).
>
> Also, the fact that __get() is not yet deprecated means that we can still
> use the aforementioned hack until/unless we’ve implemented a proper
> solution. In the worst case, you can still use a non-readonly hooked
> property and document the intended invariants in phpdoc.
>
__get is certainly not legacy; removing it would break many use cases
without proper alternatives.
The behavior after unset() has been promoted to a language feature when
readonly properties were introduced *because* it helps solve real world use
cases.
I've been asked recently by Gina if those use cases were covered by eg
native lazy proxies. The answer is *no*, because native lazy proxies cover
only part of the lazy-proxying domain: what remains is proxying by
interface and proxying internal classes, and those require a way to proxy
all property accesses, which is why magic methods are required.
With the argument that __get can be used to implement the
non-readonly-ness, we could also say that hooks are not needed, because
they can be implemented using __get. Yet, language aesthetics are
important, and we welcomed hooks for this reason. Being able to easily
lazy-init thanks to hooks on readonly would be a welcome improvement to me.
That being said, about your init proposal, I think that could work. I'd
just do it a bit differently: instead of introducing a new "init" hook, I'd
prefer having "set" mean "init" for readonly properties. But I know nothing
about the engine on the topic so I can't comment on the feasibility aspect.
I'll leave this to others.
Just a word about using hooks vs __get for lazy-init: the really hard part
when using __get is emulating the public/protected/private visibility
rules. Hooks make this a non-issue. Yet hooks - unfortunately - can't be
used as a generic lazy-init implementation because of their behavior
related to references. That's another topic, but still related, to
reinforce that __get is certainly not legacy.
>
> If a class is final and uses readonly with either hooks or __get, then
> that's the original author's choice. There's no need for extra
> engine-assisted strictness in this case. You cannot write such code in a
> non-readonly way by mistake, so it has to be by intent.
>
>
> Enforcing as strictly as possible its intended invariants is a good design
> for a robust language. Yes, it implies that users cannot (or can hardly)
> escape annoying constraints. For example, you can’t extend a final class,
> even if you think that you have good reason for it, like constructing a
> mock object.
>
That's not strictness when the root concept is already filled with
conceptual holes... I'm surprised nobody ever proposed the concept of an
*immutable* keyword, that'd be like readonly but that'd accept only
also-immutable values. Until this happens, using readonly for that is
a fallacy I'm sorry... To me that invalidates all related arguments.
Nicolas