Re: [RFC] Readonly property hooks
| From: | Nick | Date: | Fri, 11 Jul 2025 00:40:21 +0000 |
| Subject: | Re: [RFC] Readonly property hooks | ||
| References: | 1 2 3 4 5 6 7 8 9 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-127999@lists.php.net to get a copy of this message | ||
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