> Le 8 juin 2025 à 06:16, Larry Garfield <la...@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
>  la...@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

Reply via email to