Hi On 7/9/25 16:05, Larry Garfield wrote:
1. `readonly` bills 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. `readonly` has always been misnamed; it should really be `writeonce`, because that's all it is. (Once again, this is likely the most poorly designed feature we've added in many years.)
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.
2. In 8.4, if a class is marked `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, because `readonly`-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).
Readonly 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.
* 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