Re: [RFC[ Property accessor hooks, take 2

From: Date: Mon, 26 Feb 2024 23:38:31 +0000
Subject: Re: [RFC[ Property accessor hooks, take 2
References: 1 2 3 4 5 6 7 8 9 10  Groups: php.internals 
Request: Send a blank email to internals+get-122511@lists.php.net to get a copy of this message
On Tue, Feb 27, 2024, at 00:11, Frederik Bosch wrote: > Hi Rowan, > > On 26-02-2024 23:46, Rowan Tommins [IMSoP] wrote: >> On 26/02/2024 20:21, Frederik Bosch wrote: >>> I do note that $this->propName might suggest that the backing value is accessible >>> from other locations than only the property's own get/set methods, because of $this usage. >> >> >> Yes, I actually stumbled over that confusion when I was writing some of the examples in my >> lengthy e-mail in this thread. As I understand it, this would work: >> >> public string $foo { >> get { $this->foo ??= 0; $this->foo++; return $this->foo; } >> set { throw new Exception; } >> } >> >> Outside the hooks, trying to write to $this->foo would throw the exception, because it >> refers to the hooked property as a whole; but inside, the same name refers to something different, >> which isn't accessible anywhere else. >> >> Now that I've looked more at how Kotlin uses "field", I understand why it >> makes sense - it's not an alias for the property itself, but the way to access a "backing >> store" which has no other name. >> >> Using $this->foo as the name is tempting if you think of hooks as happening "on top >> of" the "real" property; but that would be a different feature, like Switft's >> "property observers" (willSet and didSet). What's really happening is that we're >> declaring two things at once, and giving them the same name; almost as if we'd written this: >> >> public string $foo { >> get { static $_foo; $_foo ??= 0; $_foo++; return $_foo; } >> set { throw new Exception; } >> } >> >> Kotlin's "field" is kind of the equivalent of that "static $_foo" >> >> > And what happens in the following situation, how are multiple get calls working together? > > public string $fullName { > get => $this->first . ' ' . $this->last; // is this accessing the backed > value, or is it accessing via get > set($value) => $this->fullName = $value; > } > > public string $first { > get => explode(' ', $this->fullName)[0], // is this accessing the backed > value, or is it accessing via get > set($value) => $value; > } > > Isn't it weird that $this->propName gives different results from one get function, > compared to the other. I would say $this->prop should always follow the same semantics as > explained in the RFC (first __get/__set, then the accessor). > >> >>> Regarding returning void=null, this is something that IDE and static analyzers already >>> pick-up as an error. I think being stricter on that in this RFC would actually make sense, and treat >>> void not as null. >> >> What would happen if a setter contained both "return 42;" and >> "return;"? The latter is explicitly allowed in "void" functions, but is also >> allowed in a non-void function as meaning "return null;" > return 42; // returns (int)42 > return; // early return, void, same as no return > return null; // returns null >> >> >>> And why yield is magic, I do not get that. The word and the expression actually >>> expresses that something is, well, yielded. >> >> But yielded to where? My mental model of "return to set" is that this: >> >> public string $name { set($value) { $x = something($value); return $x + 1; } } >> >> Is effectively: >> >> private function _name_set($value) { $x = something($value); return $x + 1; } } >> plus: >> $this->name = $this->_name_set($value); >> >> With "yield", I can't picture that simple translation; the "magic" >> is whatever translates the "yield" keyword into "$this->name =" > You would picture it by explaining how it works from the source side. A set function that > contains a yield turns the set function into a directly consumed generator. Considering the > following: > > > public string $first { > set($value) => { > yield 'First name'; > yield 'Given name'; > return 'My name'; > } > } > > the pseudo-code from the PHP source side would look as follows. > > $generator = setCall($class, 'first', $value); > foreach ($generator as $value) { > writeProperty($class, 'first', $value); > } > if ($generator->hasReturn()) { > writeProperty($class, 'first', $generator->getReturn()); > } > The yield is much more intuitive than magic fields and $this->prop (which feels like an infinite loop). Yield is remarkably simple. Looking at this, I'm still not sure what would happen here, though (maybe it is covered in the RFC, and I missed it) -- going to use yield here to try it out: public string $name { set => { if(strlen($value) < 5) { yield 'invalid'; yield $this->invalidName($value); } yield $value; } } public function invalidName($name) { return $this->name = str_pad($name, 5); } This is probably an infinite loop in this particular example, but more importantly, do setters allow reentry while executing? >> >> I would file it with the type widening in the RFC: seems kind of cool, but probably >> isn't worth the added complexity. >> >> >> Regards, > -- > Frederik Bosch > > Partner > > > Genkgo logo > > Mail: f.bosch@genkgo.nl > Web: support.genkgo.com > > Entrada 123 > Amsterdam > +31 20 244 1920 > > Genkgo B.V. staat geregistreerd bij de Kamer van Koophandel onder nummer 56501153 — Rob

« previous php.internals (#122511) next »