On Tue, Mar 12, 2024, at 10:09 PM, Rowan Tommins [IMSoP] wrote:

>> if a `get` hook for property `$foo` calls method `bar()`, then inside that 
>> method `$this->foo` will refer to the raw property, both read and write. If 
>> `bar()` is called from somewhere other than the hook, reading from 
>> `$this->foo` will trigger the `get` hook. This behavior is identical to that 
>> already used by `__get` and `__set` today. 
>
>
>
> I'm slightly confused by this.
>
> If there is an actual property called $foo, then __get and __set will 
> be called only when it is out of visibility, regardless of the call 
> stack - e.g. a private property will always trigger __get from public 
> scope, and always access it directly from private scope: 
> https://3v4l.org/R5Yos That seems differ from what's proposed, where 
> even a private call to bar() would trigger the hook.
>
> The protection against recursion appears to only be relevant for 
> completely undefined properties. For __get, the direct access can never 
> do anything useful - there's nothing to access: https://3v4l.org/2nDZS 
> For __set, it is at least possible for the non-recursive write to 
> succeed, but only in the niche case of creating a dynamic property: 
> https://3v4l.org/dpYOj I'm not sure that there's any equivalent to this 
> scenario for property hooks, since they can never be undefined/dynamic.

It's slightly different, yes.  The point is that the special behavior of a hook 
is disabled if you are within the call stack of a hook, just like the special 
behavior of __get/__set is disabled if you are within the call stack of 
__get/__set.  What happens when you hit an operation that would otherwise go 
into an infinite loop is a bit different, but the "disable to avoid an infinite 
loop" logic is the same.

So maybe "is the same" rather than "is identical", but otherwise it's the same 
concept.

>> There is one exception to the above: if a property is virtual, then there is 
>> no presumed connection between the get and set operations. [...] For that 
>> reason, `&get` by reference is allowed for virtual properties, regardless of 
>> whether or not there is a `set` hook. 
>
>
> I don't agree with this, and the example immediately following it 
> demonstrates the exact opposite: the &get and set hooks are both 
> proxying to the same backing value, and have all the same problems as 
> if the property was non-virtual. I would imagine a lot of real-life 
> virtual properties would be doing something similar: converting to/from 
> a different type, proxying to another object, etc. 
>
> I think this exception is unnecessarily complicated: either trust users 
> to handle the implications of combining &get with set, or forbid it.

The point is to give the user the option for full backwards compatibility when 
it makes sense. This requires jumping through some hoops, which is the point. 
This is essentially equivalent to creating a by-ref getter + a setter, exposing 
the underlying property. By creating a virtual property, we are "accepting" 
that the two are detached. While we could disallow this, we recognize that 
there may be valid use-cases that we'd like to enable.  It also parallels 
__get/__set, where using &__get means you can write to something without going 
through __set.

In practice I expect it virtual properties with both hooks to be very rare.  
Most virtual properties will, I expect, be lazy-computed get-only values.

>> Additionally, `&get` hooks are allowed for arrays as well, provided there is 
>> no `set` hook. 
>
>
> I mentioned in a previous e-mail the possibility of using the &get hook 
> for array writes. Has this been considered?
>
> That is:
>
> $c->arr['beep'] = 'boop';
>
> Would be equivalent to:
>
> $temp =& $c->arr;
> $temp['beep'] = 'boop';
> unset($temp);
>
> Which would be valid if $arr had an &get hook defined.

With the change to allow &get in the absence of set, I believe that would 
already work.  

cf: https://3v4l.org/3Gnti/rfc#vrfc.property-hooks

>> A `set` hook on a typed property must declare a parameter type that is the 
>> same as or contravariant (wider) from the type of the property. 
>
>> Once a property has both a `get` and `set` operation, however, it is no 
>> longer covariant or contravariant for further extension.
>
>
>
> How do these two rules interact?
>
> Could this:
>
> public string $foo { 
>    get => $this->_foo;
>    set(string|Stringable $value) {
>        $this->_foo = (string)$value;
>    } 
> }
>
> be over-ridden by this, where the property's "main type" remains 
> invariant but its "settable type" is contravariant?
>
> public string $foo { 
>    get => $this->_foo;
>    set(string|Stringable|SomethingElse $value) {
>        $this->_foo = $value instanceof SomethingElse ? 
> $value->asString() : (string)$value; 
>    } 
> }

That would be legal.


>> ReflectionProperty has several new methods to work with hooks.
>
>
> There should be some way to reliably determine the "settable type" of a 
> property. At the moment, I think you would have to do something like 
> this:
>
> $setHook = $property->getHook(PropertyHookType::Set);
> $writeType = $setHook === null ? $property->getType() : 
> $setHook->getParameters()[0]->getType();

Hm.  Good point here.  We'll probably need to add another method to 
ReflectionProperty for that.  Stay tuned.

--Larry Garfield

Reply via email to