[Including my full previous reply, since the list and gmail currently
aren't being friends. Apologies that this leads to rather a lot of
reading in one go...]
On 21/02/2024 18:55, Larry Garfield wrote:
Hello again, fine Internalians.
After much on-again/off-again work, Ilija and I are back with a more polished
property access hooks/interface properties RFC.
Hello, and a huge thanks to both you and Ilija for the continued work
on this. I'd really like to see this feature make it into PHP, and
agree with a lot of the RFC.
My main concern is the proliferation of things that look the same but
act differently, and things that look different but act the same:
var $a;
public $b;
public mixed $c;
public mixed $d = null;
public mixed $e => null;
public mixed $f { get => null };
public mixed $g { get { return null; } }
public mixed $h { get => $field }
public mixed $i { get => $this->i }
public mixed $j { get => $this->_j }
public mixed $k { => $this->k }
public mixed $l { => $this->_l }
As currently proposed:
- a and b are both what we might call "traditional" properties, and
equivalent to each other; a uses legacy syntax which we haven't
removed for some reason
- c allows the same values as b, but is a "typed property", which
changes its behaviour in various ways
- e looks like d, but is actually equivalent to f and g
- h and i are both "properties with hooks", but j is a "virtual
property", which brings additional changes in behaviour
- l allows callers to assign by reference, but k is not allowed
To make this all less confusing, I suggest the following changes:
- Remove the short-hand syntax in example e, so this sentence from the
RFC is always true: "For a property to use a hook, it must replace its
trailing |;| with a code block denoted by |{ }|."
- Allow any property to define an "" hook in place of a "get" hook
(i.e. allow example k). It is up to the user to decide whether this
will cause problems.
- Limit as much as possible the difference in behaviour between
"virtual" and "hooked" properties.
And, probably most controversially:
- Hooks should always be *on top of* the normal property defined,
unless explicitly indicated to be "virtual". Example j would thus be:
public virtual mixed $j { get => $this->_j }
This is slightly more verbose, but removes all the complexity for both
the implementation and users in determining which properties are
"virtual". I believe the time saved in reading the more explicit code
would outweigh the time spent typing the extra keyword.
Regarding the implicit $value on set hooks, I am unconvinced by the
comparison to $this, which acts more like a keyword - it is reserved
outside of methods, read-only inside them, and cannot be renamed. I
think a closer analogy would be "foreach ( $foo as $key => $value )"
or "catch ( SomeException $e )": naming $value is always required;
$key and $e can be omitted, but doing so makes the values unavailable,
it does not give them default names.
Regarding arrays, have you considered allowing array-index writes if
an hook is defined? i.e. "$x->foo['bar'] = 42;" could be treated
as semantically equivalent to "$_temp =& $x->foo; $_temp['bar'] = 42;
unset($_temp);"
As noted above, I think the user should be able to opt into this
facility for both virtual and non-virtual hooked properties, at their
own risk, for example:
class Example {
// non-virtual property, using a get hook for additional
behaviour, not to reroute the value
public array $foo {
{ $this->foo = $this->lazyLoad('foo'); return $this->foo; }
}
// ...
}
$a = new Example;
$a->foo[] = 42; // will call $a->lazyLoad('foo') to populate the
initial value, then append an item to it
The more I think about it, the more convinced I am this RFC is trying to
cram too many features into too small a space.
For instance, the ability to specify a type on the set hook. Taking the
example from the RFC:
public UnicodeString$name {
set(string|UnicodeString$value) {
$this->name = $value instanceof UnicodeString ?$value : new UnicodeString($value);
}
}
What is the type of $name? The answer is "it depends if you're writing
to or reading from it". The same use case can be covered by this:
public UnicodeString$name; public string $name_string{ get =>
(string)$this->name;
set=> $this->name = new UnicodeString($value);
}
Now we have two properties with clear types, without the complexity of
the conditional (which would be even worse if we wanted more than two
types). We can even swap the "real" and "virtual" properties transparently:
public UnicodeString$name{ get => new UnicodeString($this->name_string);
set=> $this->name_string = (string)$value;
}
public string $name_string;
This exotic "asymmetric typing" is then being used to justify other
decisions - if you can specify setter's the type,