Hey Rob,

> On 11. Jul 2025, at 01:43, Rob Landers <rob@bottled.codes> wrote:
>> 
>> 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:
> 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".
> Memoization (expensiveCalculation): only calculate the property once and only 
> once. This is a performance optimization. It is still "readonly".
> 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

Please remember that the RFC will allow `readonly` on backed properties only, 
not on virtual hooked properties. 
Nothing from your example would work, and it would result in:
Fatal error: Hooked virtual properties cannot be declared readonly
My proposed alternative implementation with caching addresses the concern 
Claude and Tim had and will make this hold:

```php
class Unusual
{
    public function __construct(
        public readonly int $value {
            get => $this->value * random_int(1, 100);
        }
    ) {}
}

$unusual = new Unusual(1);
var_dump($unusual->value === $unusual->value); // true 
```

– Nick

Reply via email to