On Fri, Jul 11, 2025, at 13:40, Nick wrote: > >> On 11. Jul 2025, at 01:43, Rob Landers <rob@bottled.codes> wrote: >> >> On Thu, Jul 10, 2025, at 17:34, Larry Garfield wrote: >>> On Thu, Jul 10, 2025, at 5:43 AM, Tim Düsterhus wrote: >>> > Hi >>> > >>> > Am 2025-07-08 17:32, schrieb Nicolas Grekas: >>> >> I also read Tim's argument that new features could be stricter. If one >>> >> wants to be stricter and forbid extra behaviors that could be added by >>> >> either the proposed hooks or __get, then the answer is : make the class >>> >> final. This is the only real way to enforce readonly-ness in PHP. >>> > >>> > Making the class final still would not allow to optimize based on the >>> > fact that the identity of a value stored in a readonly property will not >>> > change after successfully reading from the property once. Whether or not >>> > a property hooked must be considered an implementation detail, since a >>> > main point of the property hooks RFC was that hooks can be added and >>> > removed without breaking compatibility for the user of the API. >>> > >>> >> 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. >>> > >>> > That is saying "it's impossible to introduce bugs". >>> > >>> >> PS: as I keep repeating, readonly doesn't immutable at all. I know this >>> >> is >>> >> written as such in the original RFC, but the concrete definition and >>> >> implementation of readonly isn't: you can set mutable objects to >>> >> readonly >>> >> properties, and that means even readonly classes/properties are >>> >> mutable, in >>> >> the generic case. >>> > >>> > `readonly` guarantees the immutability of identity. While you can >>> > certainly mutate mutable objects, the identity of the stored object >>> > doesn't change. >>> > >>> > Best regards >>> > Tim Düsterhus >>> >>> Nick previously suggested having the get-hook's first return value cached; >>> it would still be subsequently called, so any side effects would still >>> happen (though I don't know why you'd want side effects), but only the >>> first returned value would ever get returned. Would anyone find that >>> acceptable? (In the typical case, it would be the same as the current >>> $this->foo ??= compute() pattern, just with an extra cache entry.) >>> >>> --Larry Garfield >>> >> >> I think that only covers one use-case for getters on readonly classes. Take >> this example for discussion: >> >> readonly class User { >> public int $elapsedTimeSinceCreation { get => time() - $this->createdAt; >> } >> private int $cachedResult; >> public int $totalBalance { get => $this->cachedResult ??= 5+10; } >> public int $accessLevel { get => getCurrentAccessLevel(); } >> public function __construct(public int $createdAt) {} >> } >> >> $user = new User(time() - 5); >> var_dump($user->elapsedTimeSinceCreation); // 5 >> var_dump($user->totalBalance); // 15 >> var_dump($user->accessLevel); // 42 >> >> In this example, we have three of the most common ones: >> 1. Computed Properties (elapsedTimeSinceCreation): these are properties of >> the object that are relevant to the object in question, but are not static. >> In this case, you are not writing to the object. It is still "readonly". >> 2. Memoization (expensiveCalculation): only calculate the property once and >> only once. This is a performance optimization. It is still "readonly". >> 3. External State (accessLevel): properties of the object that rely on some >> external state, which due to architecture or other convienence may not make >> sense as part of object construction. It is still "readonly". >> You can mix-and-match these to provide your own level of immutability, but >> memoization is certainly not the only one. >> >> You could make the argument that these should be functions, but I'd posit >> that these are properties of the user object. In other words, a function to >> get these values would probably be named `getElapsedTimeSinceCreation()`, >> `getTotalBalance`, or `getAccessLevel` -- we'd be writing getters anyway. >> >> — Rob > Hey Rob, > > We ended up where we are now because more people than not voiced that they > would expect a `readonly` property value to never change after `get` was > first called. > As you can see in my earlier mails I also was of a different opinion. I asked > "what if a user wants exactly that”? > You brought good examples for when “that" could be the case. > > It is correct, with the current alternative implementations your examples > would be cached. > A later call to the property would *not* use the updated time or a > potentially updated external state. > > After thinking a lot about it over the last days I think that makes sense. > > To stick to your usage of `time()`, I think the following is a good example: > > ```php > readonly class JobHelper > { > public function __construct( > public readonly string $uniqueRunnerKey { > get => 'runner/' . date("Ymd_H-i-s", time()) . '_' . (string) > random_int(1, 100) . '/'. $this->uniqueRunnerKey; > } > ) {} > } > > $helper = new JobHelper('report.txt’); > $key1 = $helper->uniqueRunnerKey; > sleep(2); > $key2 = $helper->uniqueRunnerKey; > var_dump($key1 === $key2); // true > ``` > > It has two dynamic path elements, to achieve some kind of randomness. As a > user you still can expect $key1 === $key2 to hold when using `readonly`. > > Claude's argument is strong, because we also cannot write twice to a > `readonly` property. > So it’s fair to say reading should also be predictable, and return the exact > same value on consecutive calls. > > If users don’t want that, they can opt-out by not using `readonly`. The > guarantee only holds in combination with `readonly`. > Alternatively, as you proposed, using methods (which I think would really be > a better fit; alternatively virtual properties which also will not support > `readonly`. > > With what we have now, both “camps" will be able to achieve what they want > transparently. > And I believe that’s a good middle ground we should go forward with. > > *Cheers,* > Nick
Hey Nick, After sleeping on it, I think I agree with this assessment. For backed properties, especially, it makes sense that the value feels "immutable". If and when we get to virtual properties, maybe not. But that's a bridge to cross later. — Rob