Re: [RFC] Readonly property hooks
| From: | Tim Düsterhus | Date: | Fri, 18 Jul 2025 12:32:55 +0000 |
| Subject: | Re: [RFC] Readonly property hooks | ||
| References: | 1 2 3 4 5 6 7 8 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-128104@lists.php.net to get a copy of this message | ||
Hi
On 7/9/25 16:05, Larry Garfield wrote:
1.No, readonly is readonly, not writeonce. Stop trying to redefine readonly as writeonce to justify bad design decisions. Readonly guarantees that once I successfully read from a property that I'll get the same thing out on subsequent reads and I consider this to be valuable and strongly disagree on the "most poorly designed feature" bit. Yes, I understand that __get() currently is an exception to that guarantee, but that does not mean that further exceptions should be added to water down readonly into something that is completely useless.readonlybills itself as immutability, but it fundamentally is not. There are at least two loopholes: __get and a mutable object saved to a property. So while it offering immutability guarantees is nice in theory, it's simply not true in practice.readonlyhas always been misnamed; it should really bewriteonce, because that's all it is. (Once again, this is likely the most poorly designed feature we've added in many years.)
2. In 8.4, if a class is markedReadonly is immutability of values (or in other words immutability of identity). For objects this means immutability of the object handle, for other types this means actual immutability. I also feel compelled to mention at this point that the commonly repeated statement of "Objects are passed by reference" is incorrect. It's that "the object handle is passed by value". And then it's fully consistent with how readonly works as of now.readonly, you basically forbid it from having any hooks of any kind, even though you absolutely can honor the write-once-ness of the properties while still having hooks. And that applies to child classes, too, becausereadonly-ness inherits. So adding a single hook means you have to move the readonly to all the other properties individually, which if inheritance is involved you cannot do. The RFC aims to address point 2 in a way that still respects point 1, but only point 1 as it actually is (write-once), not as we wish it to be (immutability).
* set hooks for validation, which don't impact writeonce-ness. I think everyone seems on board with that.Yes, allowing set hooks for readonly properties seems sound to me.
* Lazy computed properties. I use these a ton, even for internal caching purposes. 99% of the time I cache them because my objects are practically immutable, and $this->foo ??= whatever is an easy enough pattern. (If they're not cached then it would be a virtual property, which we're not dealing with for now.) As long as you're caching it in that fashion, the write-once-ness still ends up respected. Honestly, Nick tried to come up with examples yesterday while we were talking that would not fit into one of those two categories, and for every one of them my answer was "if your code is already that badly designed, there's nothing we can do for you." :-)It's nice to hear that there are no other usecases for hooks on readonly properties, since this means that we can just allow the 'set' hook and add an 'init' hook for the lazy computation use-case without needing to violate the semantics of
readonly by allowing a get hook.
An init hook would be clearer, certainly, though it also has its own edge cases. Can you set something that has an init hook? What happens if there's both a get and init hook? These probably have answers that could be sorted out, but that's a different question from "why the <censored> does a readonly class forbid me using even rudimentary hooks???"Not clearer. It would be the only thing that is semantically sound. While it certainly needs careful consideration of semantics to ensure there are no edge cases, figuring this out should be much easier than intentionally introducing edge cases via a get hook. As to your questions: The init hook is triggered when reading from a property that is in the uninitialized state. The return value of the hook is stored in the property and returned as the result of the read operation. Having an init hook implies the property is non-virtual. - Yes, you can set something that has an init hook. Setting means that the property will no longer be uninitialized, which means that the init hook will no longer be called. - If there is both a get and an init hook, the init hook will be called when the backing store is uninitialized. The result of the init hook will then also go through the get hook. On subsequent reads only the get hook will be called. Best regards Tim Düsterhus