Re: [RFC] Readonly property hooks
| From: | Claude Pache | Date: | Mon, 09 Jun 2025 09:51:44 +0000 |
| Subject: | Re: [RFC] Readonly property hooks | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-127629@lists.php.net to get a copy of this message | ||
> Le 8 juin 2025 à 06:16, Larry Garfield <larry@garfieldtech.com> a écrit :
>
> As Nick has graciously provided an implementation, we would like to open discussion on this
> very small RFC to allow
readonly on backed properties even if they
> have a hook defined.
>
> https://wiki.php.net/rfc/readonly_hooks
>
> --
> Larry Garfield
> larry@garfieldtech.com
Hi Larry, Nick,
Last summer, the question of allowing hooks on readonly has been raised as part of the RFC
«Property hooks improvements», and at that time I have raised an objection on allowing the get
hook on readonly properties and I have suggested for a better design for the main issue it was
supposed to solve, see https://externals.io/message/124149#124187 and
the following messages. (The RFC itself was trimmed down to the non-controversial part.) I’ll
repeat here both my objection and my proposal for better design, but more strongly, with the hope
that the message will be received.
The purpose of readonly properties is (citing the original RFC, https://wiki.php.net/rfc/readonly_properties_v2#rationale)
to provide strong immutable guarantee, i.e.:
```php
class Test {
public readonly string $prop;
public function method(Closure $fn) {
$prop = $this->prop;
$fn(); // Any code may run here.
$prop2 = $this->prop;
assert($prop === $prop2); // Always holds.
}
}
```
By allowing a get hook on readonly property, you are effectively nullifying this invariant.
Invariants must be enforced be the engines (whenever possible; there is an inevitable loophole until
the property is initialised), and not left to the discretion of the user. If a get hook on readonly
property is allowed, a random user will use its creativity in order to circumvent the intended
invariant (recall: immutability). I say “creativity”, not “dumbness”, because you cannot
mechanically tell the two apart:
```php
class doc {
public readonly int page {
get => $this->page + $this->offset;
}
private int $offset = 0;
public function __construct(int $page) {
$this->page = $page;
}
public function foo() {
// $this->offset may be adjusted here
}
}
```
I know that some people won’t see a problem with that code (see the cited thread above), and this
is a strong reason not to allow that: you cannot trust the user to enforce invariants that they
don’t understand or are not interested in.
(The objection above is for the get hook; there is no such issue with the
set` hook.)
Now, here is the suggestion for a better alternative design, that (1) don’t allow to break the
invariant of immutability, (2) solve the issue of lazy initialisation (which is, I guess, the main
purpose of the get hook on readonly), and (3) also works with nullable properties:
Add an additional hook to backed properties, named init. When attempting to read the
value of the backing store, if it is uninitialised, then the init hook is triggered, which is
supposed to initialise it.
—Claude