On 08/03/2024 15:53, Larry Garfield wrote:
Hi folks.  Based on earlier discussions, we've made a number of changes
to the RFC that should address some of the concerns people raised.  We
also had some very fruitful discussions off-list with several developers
  from the Foundation, which led to what we feel are some solid
improvements.

https://wiki.php.net/rfc/property-hooks


Hi Larry,

Thanks again for the continuing hard work on this!


> 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.



> 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.



> 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.



> 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;
   }
}


> 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();



Once again, I would like to make the case that asymmetric types are an unnecessary complication that should be left to Future Scope.

The fact that none of the other languages referenced have such a feature should also give us pause. There's nothing to stop us being the first to innovate a feature, but we should be extra cautious when doing so, with no previous experience to learn from. It also means there is no expectation from users coming from other languages that this will be possible.

If it genuinely seems useful, it can be added in a follow-up RFC, or even a later version of PHP, with little impact on the rest of the feature. But if we add it now and regret it, or some detail of its implementation, we will be stuck with it forever.


Regards,

--
Rowan Tommins
[IMSoP]

Reply via email to