# [RFC[ Property accessor hooks, take 2

- Thread: https://externals.io/message/122445
- Original archive: https://news-web.php.net/php.internals/122445
- Started by Larry Garfield on 2024-02-21T18:55:49+00:00
- Messages: 80
- RFC: Property access hooks (Implemented (PHP 8.4)) — https://externals.io/rfc/property-hooks.md
- Page 1 of 4: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/122445.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #122445 — Larry Garfield — 2024-02-21T18:55:49+00:00

URL: https://externals.io/message/122445#122445

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.  It’s 99% unchanged from last summer; the PR is now essentially complete and more robust, and we were able to squish the last remaining edge cases.

Baring any major changes, we plan to bring this to a vote in mid-March.

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

It’s long, but that’s because we’re handling every edge case we could think of.  Properties involve dealing with both references and inheritance, both of which have complex implications.  We believe we’ve identified the most logical handling for all cases, though.

Note the FAQ question at the end, which explains some design choices.

There’s one outstanding question, which is slightly painful to ask: Originally, this RFC was called “property accessors,” which is the terminology used by most languages.  During early development, when we had 4 accessors like Swift, we changed the name to “hooks” to better indicate that one was “hooking into” the property lifecycle.  However, later refinement brought it back down to 2 operations, get and set.  That makes the “hooks” name less applicable, and inconsistent with what other languages call it.

However, changing it back at this point would be a non-small amount of grunt work. There would be no functional changes from doing so, but it’s lots of renaming things both in the PR and the RFC. We are willing to do so if the consensus is that it would be beneficial, but want to ask before putting in the effort.

-- 
  Larry Garfield
  larry@garfieldtech.com

---

## #122446 — Erick de Azevedo Lima — 2024-02-21T22:43:15+00:00

URL: https://externals.io/message/122445#122446 · In reply to: #122445

It's really nice!

> > […]
beneficial, but want to ask before putting in the effort.
I really think that it's not necessary, as the it still hooks into the
default behavior of the properties.

Even though I don't have voting privileges, I'm rooting a lot for it. Thank
you again for this amazing work!

--
Erick

---

## #122447 — Pierre — 2024-02-21T22:50:04+00:00

URL: https://externals.io/message/122445#122447 · In reply to: #122445

Le 21/02/2024 à 19:55, Larry Garfield a écrit :
> 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.  It’s 99% unchanged from last summer; the PR is now essentially complete and more robust, and we were able to squish the last remaining edge cases.
>
> Baring any major changes, we plan to bring this to a vote in mid-March.
>
> https://wiki.php.net/rfc/property-hooks
>
> It’s long, but that’s because we’re handling every edge case we could think of.  Properties involve dealing with both references and inheritance, both of which have complex implications.  We believe we’ve identified the most logical handling for all cases, though.
>
> Note the FAQ question at the end, which explains some design choices.
>
> There’s one outstanding question, which is slightly painful to ask: Originally, this RFC was called “property accessors,” which is the terminology used by most languages.  During early development, when we had 4 accessors like Swift, we changed the name to “hooks” to better indicate that one was “hooking into” the property lifecycle.  However, later refinement brought it back down to 2 operations, get and set.  That makes the “hooks” name less applicable, and inconsistent with what other languages call it.
>
> However, changing it back at this point would be a non-small amount of grunt work. There would be no functional changes from doing so, but it’s lots of renaming things both in the PR and the RFC. We are willing to do so if the consensus is that it would be beneficial, but want to ask before putting in the effort.
>
Yes please ! Pass !

I don't have voting rights, but we need this.

Cheers,

Pierre R.

---

## #122448 — Matthew Weier O'Phinney — 2024-02-21T23:02:38+00:00

URL: https://externals.io/message/122445#122448 · In reply to: #122445

On Wed, Feb 21, 2024 at 12:57 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> After much on-again/off-again work, Ilija and I are back with a more
> polished property access hooks/interface properties RFC.  It’s 99%
> unchanged from last summer; the PR is now essentially complete and more
> robust, and we were able to squish the last remaining edge cases.
>
> Baring any major changes, we plan to bring this to a vote in mid-March.
>
> https://wiki.php.net/rfc/property-hooks
>
> It’s long, but that’s because we’re handling every edge case we could
> think of.  Properties involve dealing with both references and inheritance,
> both of which have complex implications.  We believe we’ve identified the
> most logical handling for all cases, though.
>

Once again in reading the proposal, the first thing I'm struck by are the
magic "$field" and "$value" variables inside accessors. The first time they
are used, they're used without explanation, and they're jarring.

Additionally, once you start defining the behavior of accessors... you
don't start with the basics, but instead jump into some of the more
esoteric usage, which does nothing to help with the questions I have.

So, first:

- Start with the most basic, most expected usage for each of reading and
writing properties.
- I need a better argument for why the $field and $value variables exist.
Saying they're macros doesn't help those not deep into internals. As a
user, why do they exist?

Honestly, it's not obvious at all that I can just set something to the
variable "$field", and if I didn't know about the feature and stumbled
across a class that used accessors, I'd be wondering what "$field" is, and
how the property ever gets set. (And yes, I've read the FAQ. Saying it's
used in another language doesn't automatically make it good design.)

I know you say in the narrative that you _can_ use `$this->propertyName = `
to set the value, but you also say it's _not recommended_ (though you do
not indicate _why_). To somebody coming primarily from userland who's been
doing OOP in PHP for over 20 years, it's far more clear what's happening.
Alternately, I'd argue that both the accessors should require an argument
that represents the reference to the property:

    public string $username {
        set($field, string|Stringable $value) {
            $field = (string) $value;
        }
        get ($field) => strtolower($field);
    }

The same is true for $value. I'd recommend only ever allowing the construct
`set ($value) {}`, as it's immediately clear that `$value` is the argument
and value being provided to the accessor. When it's implicit, it's easy to
lose context.

Second: you don't have examples of defining BOTH get and set OTHER than
when using expressions for both accessors or a mix. I'm actually unclear
what the syntax is when both are defined. Is there supposed to be a `;`
terminating each? Or  a `,`? Or just an empty line? Again, this is one of
the more common scenarios. It needs to be covered early, and clearly.

Third: the caveats around usage with arrays... give me pause. While I'm
personally trying to not use arrays as much as possible, a lot of code I
see or contribute to still does, and the fact that an array property that
uses a write accessor doesn't allow the same level of access as a normal
array property is something I see leading to a lot of confusion and errors.
I don't have a solution, but I worry that this one thing alone could be
enough to prevent the passage of the RFC.

Fourth: the syntax around inheritance is not intuitive, as it does not work
in the same way as the rest of the language. I'm talking about this:

    public int $x {
        get: 2 * parent::$x::get()
    }

Why do we need to use the accessors here? Why wouldn't it just be
`parent::$x`?

I want to be clear: I really like the idea behind this feature, and
overall, I appreciate the design. From a user perspective, though, the
above are things that I found jarring as they vary quite a bit from our
current language design.

>
> Note the FAQ question at the end, which explains some design choices.
>
> There’s one outstanding question, which is slightly painful to ask:
> Originally, this RFC was called “property accessors,” which is the
> terminology used by most languages.  During early development, when we had
> 4 accessors like Swift, we changed the name to “hooks” to better indicate
> that one was “hooking into” the property lifecycle.  However, later
> refinement brought it back down to 2 operations, get and set.  That makes
> the “hooks” name less applicable, and inconsistent with what other
> languages call it.
>
> However, changing it back at this point would be a non-small amount of
> grunt work. There would be no functional changes from doing so, but it’s
> lots of renaming things both in the PR and the RFC. We are willing to do so
> if the consensus is that it would be beneficial, but want to ask before
> putting in the effort.
>

I personally would go with just "accessors".


-- 
Matthew Weier O'Phinney
mweierophinney@gmail.com
https://mwop.net/
he/him

---

## #122449 — Deleu — 2024-02-22T00:06:06+00:00

URL: https://externals.io/message/122445#122449 · In reply to: #122445

On Wed, Feb 21, 2024 at 3:58 PM Larry Garfield <larry@garfieldtech.com>
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.  It’s 99%
> unchanged from last summer; the PR is now essentially complete and more
> robust, and we were able to squish the last remaining edge cases.
>
> Baring any major changes, we plan to bring this to a vote in mid-March.
>
> https://wiki.php.net/rfc/property-hooks
>
> It’s long, but that’s because we’re handling every edge case we could
> think of.  Properties involve dealing with both references and inheritance,
> both of which have complex implications.  We believe we’ve identified the
> most logical handling for all cases, though.
>
> Note the FAQ question at the end, which explains some design choices.
>
> There’s one outstanding question, which is slightly painful to ask:
> Originally, this RFC was called “property accessors,” which is the
> terminology used by most languages.  During early development, when we had
> 4 accessors like Swift, we changed the name to “hooks” to better indicate
> that one was “hooking into” the property lifecycle.  However, later
> refinement brought it back down to 2 operations, get and set.  That makes
> the “hooks” name less applicable, and inconsistent with what other
> languages call it.
>
> However, changing it back at this point would be a non-small amount of
> grunt work. There would be no functional changes from doing so, but it’s
> lots of renaming things both in the PR and the RFC. We are willing to do so
> if the consensus is that it would be beneficial, but want to ask before
> putting in the effort.
>
> --
>   Larry Garfield
>   larry@garfieldtech.com
>

This is a long, huge and comprehensive work, congratz to the authors.

It clearly shows that so much thought and work has been put into it that it
makes me cautious to even ask for further clarification.

> Javascript have similar features via a different syntax, although that
syntax would not be viable for PHP

Why not?

```
final class Foo
{
    public string $bar;

    public function get bar(): string
    {
        // custom getter
    }

    public function set bar(string $value): void
    {
        // custom setter
    }
}
```

It feels quite a natural syntax for PHP and from someone oblivious to the
internal work, it appears to be a slight marginal change to the existing
RFC. Given the extensive work of this RFC, it seems pretty obvious that
this syntax will not work, I just don't know why.

-- 
Marco Deleu

---

## #122450 — Robert Landers — 2024-02-22T01:14:32+00:00

URL: https://externals.io/message/122445#122450 · In reply to: #122447

>
> Le 21/02/2024 à 19:55, Larry Garfield a écrit :
> > […]
> Yes please ! Pass !
>
> I don't have voting rights, but we need this.
>
> Cheers,
>
> Pierre R.

I apologize if this has already been covered:

> There are two shorthand notations supported, beyond the optional argument to set.
> First, if a hook's body is a single expression, then the { } and return statement may be omitted and replaced with =>, just like with arrow functions.

Does => do any special auto-capturing of variables like arrow
functions or is it just a shorthand? Also, is this a meaningful
shorthand to the example a little further down:

public string $phone {
    set = $this->sanitizePhone(...);
}

or do we always have to write it out?

public string $phone {
    set => $field = $this->sanitizePhone($value);
}

Would __PROPERTY__ be set inside sanitizePhone() as well?

You mention several ways values are displayed (whether or not they use
the get-hook), but what does the default implementation of
`__debugInfo()` look like now (or is that out of scope or a silly
question?)

For attributes, it would be nice to be able to target hooks
specifically with attributes instead of also all methods (e.g., a
Attribute::TARGET_GET_HOOK const). For example, if I were writing a
serialization library, I may want to specify #[UseRawValue] only on
getters to ensure that only the raw value is serialized instead of the
getter (which may be specific to the application logic, or
#[GetFromMethod] to tell the serialization library to get the value
from a completely different method. It wouldn't make sense to target
just any method with that attribute.

---

## #122451 — Robert Landers — 2024-02-22T08:34:40+00:00

URL: https://externals.io/message/122445#122451 · In reply to: #122445

>
> 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.  It’s 99% unchanged from last summer; the PR is now essentially complete and more robust, and we were able to squish the last remaining edge cases.
>
> Baring any major changes, we plan to bring this to a vote in mid-March.
>
> https://wiki.php.net/rfc/property-hooks
>
> It’s long, but that’s because we’re handling every edge case we could think of.  Properties involve dealing with both references and inheritance, both of which have complex implications.  We believe we’ve identified the most logical handling for all cases, though.
>
> Note the FAQ question at the end, which explains some design choices.
>
> There’s one outstanding question, which is slightly painful to ask: Originally, this RFC was called “property accessors,” which is the terminology used by most languages.  During early development, when we had 4 accessors like Swift, we changed the name to “hooks” to better indicate that one was “hooking into” the property lifecycle.  However, later refinement brought it back down to 2 operations, get and set.  That makes the “hooks” name less applicable, and inconsistent with what other languages call it.
>
> However, changing it back at this point would be a non-small amount of grunt work. There would be no functional changes from doing so, but it’s lots of renaming things both in the PR and the RFC. We are willing to do so if the consensus is that it would be beneficial, but want to ask before putting in the effort.
>
> --
>   Larry Garfield
>   larry@garfieldtech.com

This is a reply to Marco (https://externals.io/message/122445#122449)
for which I didn't actually receive the email but got an email from
the list that I didn't receive the email -- seems like it would have
been simpler and more correct to resend the email. That is a bit
weird, but whatever.

> Given the extensive work of this RFC, it seems pretty obvious that
> this syntax will not work, I just don't know why.

I feel like the syntax is natural, coming from other languages with
this feature. However, I really do appreciate the example Marco gives
as it feels very idiomatically PHP with all the getters/setters we are
used to writing. It also means a pretty simple "just add a space and
downcase" to switch from traditional methods to getters/setters to
hooks, which could be really nice.

---

## #122452 — tag Knife — 2024-02-22T11:22:22+00:00

URL: https://externals.io/message/122445#122452 · In reply to: #122445

> 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.  It’s 99%
> unchanged from last summer; the PR is now essentially complete and more
> robust, and we were able to squish the last remaining edge cases.
>
> Baring any major changes, we plan to bring this to a vote in mid-March.
>
> https://wiki.php.net/rfc/property-hooks
>
> It’s long, but that’s because we’re handling every edge case we could
> think of.  Properties involve dealing with both references and inheritance,
> both of which have complex implications.  We believe we’ve identified the
> most logical handling for all cases, though.
>
> Note the FAQ question at the end, which explains some design choices.
>
> There’s one outstanding question, which is slightly painful to ask:
> Originally, this RFC was called “property accessors,” which is the
> terminology used by most languages.  During early development, when we had
> 4 accessors like Swift, we changed the name to “hooks” to better indicate
> that one was “hooking into” the property lifecycle.  However, later
> refinement brought it back down to 2 operations, get and set.  That makes
> the “hooks” name less applicable, and inconsistent with what other
> languages call it.
>
> However, changing it back at this point would be a non-small amount of
> grunt work. There would be no functional changes from doing so, but it’s
> lots of renaming things both in the PR and the RFC. We are willing to do so
> if the consensus is that it would be beneficial, but want to ask before
> putting in the effort.
>
> --
>   Larry Garfield
>   larry@garfieldtech.com


I remember the previous RFC. cant remeber why it was decline, but i hope
this
one passes, I know the PHP community has been asking for native
getter/setters
instead of __get __set for a while, since 7.0 was released at least.

A few things i was interested to get the idea around.
Was it thought about for the set{} for it to return the value to set the
property to
instead implicitly setting its own field?

eg

```
public string $name {
    set {
        return usfirst($value);
    }
}
```
Where the value returned in the set is what the property will be set to?

---

## #122453 — Deleu — 2024-02-22T12:58:40+00:00

URL: https://externals.io/message/122445#122453 · In reply to: #122452

> A few things i was interested to get the idea around.
> Was it thought about for the set{} for it to return the value to set the
> property to
> instead implicitly setting its own field?
>
> eg
>
> ```
> public string $name {
>     set {
>         return usfirst($value);
>     }
> }
> ```
> Where the value returned in the set is what the property will be set to?
>

The answer to this question is best described on the RFC FAQ:
https://wiki.php.net/rfc/property-hooks#why_do_set_hooks_not_return_the_value_to_set

-- 
Marco Deleu

---

## #122473 — tim@bastelstu.be — 2024-02-22T19:39:11+00:00

URL: https://externals.io/message/122445#122473 · In reply to: #122448

Hi


> Once again in reading the proposal, the first thing I'm struck by are the
> magic "$field" and "$value" variables inside accessors. The first time they
> are used, they're used without explanation, and they're jarring.
> 
> […]
> 
> Second: you don't have examples of defining BOTH get and set OTHER than
> when using expressions for both accessors or a mix. I'm actually unclear
> what the syntax is when both are defined. Is there supposed to be a `;`
> terminating each? Or  a `,`? Or just an empty line? Again, this is one of
> the more common scenarios. It needs to be covered early, and clearly.
> 

On a similar topic with regard to syntax and shorthands, I'd like to 
quote myself from my previous year's email (Message-ID: 
17d7983b-68b7-e273-a445-f8399c2510cc@bastelstu.be, 
https://externals.io/message/120213#120216):

> (5) I strongly dislike the doubly abbreviated form of public string $fullName => $this->first . " " . $this->last;. Having just the extra
> '>' in there to distinguish it from a regular property feels non-obvious.

It's always possible to follow-up with syntax that allows for additional 
brevity, the inverse is not true and I believe for such a semantically 
complex feature, having clear and syntax syntax would be beneficial.

Best regards
Tim Düsterhus

---

## #122474 — tim@bastelstu.be — 2024-02-22T19:53:17+00:00

URL: https://externals.io/message/122445#122474 · In reply to: #122445

Hi

1. I believe there is an error in the "Final Hooks" section:

 > // But this is NOT allowed, because beforeSet is final in the parent.

I believe it should be s/beforeSet/set/.

2. In the same section this sentence is probably grammatically incorrect.

 > Declaring hooks final on a property that is declared final is 
redundant will throw an error.

3. Regarding the same sentence I also don't see why that should be 
disallowed, even if it is redundant. It's also legal to define 'final' 
functions within a 'final' class. Which also brings me to the question: 
Is defining 'final' properties and 'final' hooks within a 'final' class 
equally disallowed?

4. Since the RFC was last discussed, the #[\Override] RFC was accepted 
and implemented (https://wiki.php.net/rfc/marking_overriden_methods). A 
short section on the interaction with #[\Override] would probably be 
helpful.

5. Not sure if I've asked it before, but have you considered making the 
parameter for ReflectionProperty::getHook() an enum?


> There’s one outstanding question, which is slightly painful to ask: Originally, this RFC was called “property accessors,” which is the terminology used by most languages.  During early development, when we had 4 accessors like Swift, we changed the name to “hooks” to better indicate that one was “hooking into” the property lifecycle.  However, later refinement brought it back down to 2 operations, get and set.  That makes the “hooks” name less applicable, and inconsistent with what other languages call it.
> 
> However, changing it back at this point would be a non-small amount of grunt work. There would be no functional changes from doing so, but it’s lots of renaming things both in the PR and the RFC. We are willing to do so if the consensus is that it would be beneficial, but want to ask before putting in the effort.
> 

Calling it hooks is fine and allows for future extension without 
renaming, should the need arise.

Best regards
Tim Düsterhus

---

## #122476 — Larry Garfield — 2024-02-22T23:56:05+00:00

URL: https://externals.io/message/122445#122476 · In reply to: #122448

> 
> > […]
>
> Once again in reading the proposal, the first thing I'm struck by are 
> the magic "$field" and "$value" variables inside accessors. The first 
> time they are used, they're used without explanation, and they're 
> jarring.
>
> Additionally, once you start defining the behavior of accessors... you 
> don't start with the basics, but instead jump into some of the more 
> esoteric usage, which does nothing to help with the questions I have.
>
> So, first:
>
> - Start with the most basic, most expected usage for each of reading 
> and writing properties.
> - I need a better argument for why the $field and $value variables 
> exist. Saying they're macros doesn't help those not deep into 
> internals. As a user, why do they exist?

For $field, it's not a requirement.  It's mostly for copy-paste convenience.  A number of people have struggled on this point so if the consensus is to leave out $field and just use $this->propName directly, we can accept that.  They can be re-added if reusable hook packages are added in the future (as noted in Future Scope).

For $value, it's to avoid boilerplate.  For the majority case, you'll be just operating on an individual value trivially.  Checking it's range, or uppercasing it, or whatever.  Requiring the developer to provide a name explicitly is just extra work; it's much the same as how PHP doesn't require you to pass $this as the first argument to a method explicitly, the way Python and Rust do.  It's just understood that $this exists, and once you learn that it's obvious.

On the occasions where you do want to specify an alternate name for some reason, or more likely you're providing a wider type, you can.  But in the typical case it would just be one more thing for the dev to have to type out.  This is especially true in what I expect to be a common case, which is promoted constructor arguments with an extra validator set hook on them.

It also introduces some ambiguity.  If I specify only the name, does that mean I'm widening the type to mixed?  Or just that I'm omitting the name?  If specifying the name is rare, that's not really a big deal.  If it's required in every case, it's a confusion point  in every case.

In the interest of transparency. for comparison:

* Kotlin appears to require an argument name, but by convention recommends using "value".
* Swift makes it optional, with a default name of "newValue".  (Same logic as in the RFC.)
* C# ... as far as I can tell, doesn't support a custom name at all; it's always called "value", implicitly.

> Second: you don't have examples of defining BOTH get and set OTHER than 
> when using expressions for both accessors or a mix. I'm actually 
> unclear what the syntax is when both are defined. Is there supposed to 
> be a `;` terminating each? Or  a `,`? Or just an empty line? Again, 
> this is one of the more common scenarios. It needs to be covered early, 
> and clearly.

... huh.  I thought we had one in there somewhere.  I will add one, thanks.  Though to clarify, there's no separator character.

public string $foo {
    get {
        // ...
    }
    set {
        // ...
    }
}

> Third: the caveats around usage with arrays... give me pause. While I'm 
> personally trying to not use arrays as much as possible, a lot of code 
> I see or contribute to still does, and the fact that an array property 
> that uses a write accessor doesn't allow the same level of access as a 
> normal array property is something I see leading to a lot of confusion 
> and errors. I don't have a solution, but I worry that this one thing 
> alone could be enough to prevent the passage of the RFC.

We completely agree that it's a suboptimal situation.  But as explained, it is the way it is because it's not possible (as far as we can tell) to fully support hooks on array properties.  If you can think of one, please share, because we'd love to make this part better.  I don't like it either, but we haven't found a way around it.  And that caveat doesn't seem like a good enough reason to not support hooks everywhere we actually can.


> Fourth: the syntax around inheritance is not intuitive, as it does not 
> work in the same way as the rest of the language. I'm talking about 
> this:
>
>     public int $x {
>         get: 2 * parent::$x::get()
>     }
>
> Why do we need to use the accessors here? Why wouldn't it just be `parent::$x`?

Almost.  Ilija spent some time looking into this, and it's possible with caveats.

First, there's then no way to differentiate between "access parent hook" and "read the static property $x on the parent".  Arguably that's not a common case, but it is a point of confusion.

The larger issue is that parent::$x can't be just a stand-in for the backing value in all cases.  While supporting that for the = operator is straightforward enough, it wouldn't give us access to ++, --, <=, and the dozen or so other operators that could conceivably apply.  In theory those could all be implemented manually, but Ilija described that as "hundreds to thousands of lines of code" to do, which... is not time or code well spent. :-)  Especially as this is a very edge-case situation to begin with.  (In practice, I expect auto-generated ORM proxy code to be the primary user of accessing a parent property from a child hook.  I can think of very few other cases where I'd want to use it.)

So we have the choice between making $a = parent::$prop and parent::$prop = $a work, but *nothing else*, inexplicably (creating confusion) or the slightly longer syntax that wouldn't support those other operations anyway so there's no confusion.

We feel the current approach is the better trade off, but if the consensus generally is for the shorter-but-inconsistent syntax, that can be changed.

> I want to be clear: I really like the idea behind this feature, and 
> overall, I appreciate the design. From a user perspective, though, the 
> above are things that I found jarring as they vary quite a bit from our 
> current language design.
> > […]
>
> I personally would go with just "accessors". 



> This is a long, huge and comprehensive work, congratz to the authors.
>
> It clearly shows that so much thought and work has been put into it 
> that it makes me cautious to even ask for further clarification. 
>
> > […]
>
> Why not?

> It feels quite a natural syntax for PHP and from someone oblivious to 
> the internal work, it appears to be a slight marginal change to the 
> existing RFC. Given the extensive work of this RFC, it seems pretty 
> obvious that this syntax will not work, I just don't know why.

I've added an FAQ section explaining why the Python/JS approach wouldn't really work.  To be clear, Ilija and I spent 100+ hours doing research and design before we started implementation (back in mid-late 2022).  We did seriously consider the JS-style syntax, but in the end we found it created more problems than it solves.  For the type of language PHP is (explicit typed properties), doing it on the property itself is a much cleaner approach.




> I apologize if this has already been covered:
>
> > […]
>
> Does => do any special auto-capturing of variables like arrow
> functions or is it just a shorthand? 

No, there is nothing to capture.  Inside the hook body you have $this the same as any other method.

> Also, is this a meaningful
> shorthand to the example a little further down:
>
> public string $phone {
>     set = $this->sanitizePhone(...);
> }
>
> or do we always have to write it out?
>
> public string $phone {
>     set => $field = $this->sanitizePhone($value);
> }

Currently no, the set is always void; you have to write the value yourself.  See the FAQ section on the topic for a detailed explanation.

However, I just had a long discussion with Ilija and there is one possibility we could consider: Use the return value only on the shorthand (arrow-function-like) syntax.

So you could do either of these, which would be equivalent:

set {
  $this->phone = $this->santizePhone($value);
}

set => $this->santizePhone($value);

This would have the advantage of offering a return-to-set mechanism, as well as even shorter syntax in the simple case (and no question of $field vs $this->propName).  But it would have the disadvantage of being potentially inconsistent between the short and long version.  It also would mean the short version is incompatible with virtual properties; using a short-set would create a backing value, so it's non-virtual.  But since "simple validation, maybe in a promoted constructor property" is likely to be one of the main use cases, it would simplify that case.

Not sure if that's a trade up or not.  Thoughts from the list?

> Would __PROPERTY__ be set inside sanitizePhone() as well?

No.  Like __CLASS__, it is materialized at compile time and has no meaning outside of its intended scope.

> You mention several ways values are displayed (whether or not they use
> the get-hook), but what does the default implementation of
> `__debugInfo()` look like now (or is that out of scope or a silly
> question?)

 `var_dump()` shows the underlying backing value, bypassing get, since it's debugging the object state.  If you implement __debugInfo(), that's a method like any other and you can do what you like, though you'll be reading through get hooks (just like using __serialize()).

> For attributes, it would be nice to be able to target hooks
> specifically with attributes instead of also all methods (e.g., a
> Attribute::TARGET_GET_HOOK const). For example, if I were writing a
> serialization library, I may want to specify #[UseRawValue] only on
> getters to ensure that only the raw value is serialized instead of the
> getter (which may be specific to the application logic, or
> #[GetFromMethod] to tell the serialization library to get the value
> from a completely different method. It wouldn't make sense to target
> just any method with that attribute.

This feels very niche, honestly.  Those would naturally have to be sub-cases of TARGET_METHOD anyway, so method-targeted attributes would need to be supported regardless.  That makes hook-specific-targeting an easy and non-breaking add-on for a future RFC if it turns out to be useful in practice.

--Larry Garfield

---

## #122477 — Stephen Reay — 2024-02-23T08:33:16+00:00

URL: https://externals.io/message/122445#122477 · In reply to: #122476

> 
> 
> 
> > […]
> 
> For $field, it's not a requirement.  It's mostly for copy-paste convenience.  A number of people have struggled on this point so if the consensus is to leave out $field and just use $this->propName directly, we can accept that.  They can be re-added if reusable hook packages are added in the future (as noted in Future Scope).
> 
> For $value, it's to avoid boilerplate.  For the majority case, you'll be just operating on an individual value trivially.  Checking it's range, or uppercasing it, or whatever.  Requiring the developer to provide a name explicitly is just extra work; it's much the same as how PHP doesn't require you to pass $this as the first argument to a method explicitly, the way Python and Rust do.  It's just understood that $this exists, and once you learn that it's obvious.
> 
> On the occasions where you do want to specify an alternate name for some reason, or more likely you're providing a wider type, you can.  But in the typical case it would just be one more thing for the dev to have to type out.  This is especially true in what I expect to be a common case, which is promoted constructor arguments with an extra validator set hook on them.
> 
> It also introduces some ambiguity.  If I specify only the name, does that mean I'm widening the type to mixed?  Or just that I'm omitting the name?  If specifying the name is rare, that's not really a big deal.  If it's required in every case, it's a confusion point  in every case.
> 
> In the interest of transparency. for comparison:
> 
> * Kotlin appears to require an argument name, but by convention recommends using "value".
> * Swift makes it optional, with a default name of "newValue".  (Same logic as in the RFC.)
> * C# ... as far as I can tell, doesn't support a custom name at all; it's always called "value", implicitly.
> 
> > […]
> 
> ... huh.  I thought we had one in there somewhere.  I will add one, thanks.  Though to clarify, there's no separator character.
> 
> public string $foo {
>    get {
>        // ...
>    }
>    set {
>        // ...
>    }
> }
> 
> > […]
> 
> We completely agree that it's a suboptimal situation.  But as explained, it is the way it is because it's not possible (as far as we can tell) to fully support hooks on array properties.  If you can think of one, please share, because we'd love to make this part better.  I don't like it either, but we haven't found a way around it.  And that caveat doesn't seem like a good enough reason to not support hooks everywhere we actually can.
> 
> 
> > […]
> 
> Almost.  Ilija spent some time looking into this, and it's possible with caveats.
> 
> First, there's then no way to differentiate between "access parent hook" and "read the static property $x on the parent".  Arguably that's not a common case, but it is a point of confusion.
> 
> The larger issue is that parent::$x can't be just a stand-in for the backing value in all cases.  While supporting that for the = operator is straightforward enough, it wouldn't give us access to ++, --, <=, and the dozen or so other operators that could conceivably apply.  In theory those could all be implemented manually, but Ilija described that as "hundreds to thousands of lines of code" to do, which... is not time or code well spent. :-)  Especially as this is a very edge-case situation to begin with.  (In practice, I expect auto-generated ORM proxy code to be the primary user of accessing a parent property from a child hook.  I can think of very few other cases where I'd want to use it.)
> 
> So we have the choice between making $a = parent::$prop and parent::$prop = $a work, but *nothing else*, inexplicably (creating confusion) or the slightly longer syntax that wouldn't support those other operations anyway so there's no confusion.
> 
> We feel the current approach is the better trade off, but if the consensus generally is for the shorter-but-inconsistent syntax, that can be changed.
> 
> > […]
> 
> 
> 
> > […]
> 
> > […]
> 
> I've added an FAQ section explaining why the Python/JS approach wouldn't really work.  To be clear, Ilija and I spent 100+ hours doing research and design before we started implementation (back in mid-late 2022).  We did seriously consider the JS-style syntax, but in the end we found it created more problems than it solves.  For the type of language PHP is (explicit typed properties), doing it on the property itself is a much cleaner approach.
> 
> 
> 
> 
> > […]
> 
> No, there is nothing to capture.  Inside the hook body you have $this the same as any other method.
> 
> > […]
> 
> Currently no, the set is always void; you have to write the value yourself.  See the FAQ section on the topic for a detailed explanation.
> 
> However, I just had a long discussion with Ilija and there is one possibility we could consider: Use the return value only on the shorthand (arrow-function-like) syntax.
> 
> So you could do either of these, which would be equivalent:
> 
> set {
>  $this->phone = $this->santizePhone($value);
> }
> 
> set => $this->santizePhone($value);
> 
> This would have the advantage of offering a return-to-set mechanism, as well as even shorter syntax in the simple case (and no question of $field vs $this->propName).  But it would have the disadvantage of being potentially inconsistent between the short and long version.  It also would mean the short version is incompatible with virtual properties; using a short-set would create a backing value, so it's non-virtual.  But since "simple validation, maybe in a promoted constructor property" is likely to be one of the main use cases, it would simplify that case.
> 
> Not sure if that's a trade up or not.  Thoughts from the list?
> 
> > […]
> 
> No.  Like __CLASS__, it is materialized at compile time and has no meaning outside of its intended scope.
> 
> > […]
> 
> `var_dump()` shows the underlying backing value, bypassing get, since it's debugging the object state.  If you implement __debugInfo(), that's a method like any other and you can do what you like, though you'll be reading through get hooks (just like using __serialize()).
> 
> > […]
> 
> This feels very niche, honestly.  Those would naturally have to be sub-cases of TARGET_METHOD anyway, so method-targeted attributes would need to be supported regardless.  That makes hook-specific-targeting an easy and non-breaking add-on for a future RFC if it turns out to be useful in practice.
> 
> --Larry Garfield

Hi Larry,

It's good to see this idea still progressing.


I have to agree with the other comment(s) that the implicit `$field`/`$value` variables seem odd to me. I understand the desire for brevity and the potential future scope of reused hooks, but this concept seems to fly in the face of many years of PHP reducing "magic" like this.

To give one answer to your question about ambiguity if the `$value` parameter is required - I don't believe this is actually ambiguous, in the context of PHP:
- method parameters in child classes don't implicitly 'inherit' the parent method parameter's type if they don't define one (they widen to mixed);
- method return types have no implicit inheritance, they must declare a compatible return type;
- typed class properties don't implicitly inherit the parent type when the type left off a child property - they must declare the same type.

AFAIK there is no existing behaviour in PHP where omitting a type would mean "the type is implicitly inherited from X", it either means the same as mixed, or it's an error.


The use of implicit variables like this also opens up a new potential for bugs, particularly in the `set` block, where the difference between setting a new local variable inside the method body and writing to the backing store is indistinguishable without knowing intent. Explicitly writing to `$this->propertyName` (or `$this->{__PROPERTY__}` in a hypothetical re-used/generated hook) is certainly longer, but it's also much less likely to suffer from a typo that is then missed by a test, especially given that dynamic properties are now deprecated, so writing to `$this->porpertyName` from a hook on `$propertyName` would likely trigger an error, while writing to `$feild` from a hook would produce no error.

Ultimately so long as the syntax *allows* for being explicit (which it seems to, and it provides the magic constant allowing for future dynamic use in reused hooks) I'm generally positive about this.

Given that these are both essentially deemed as "shortcuts" for convenience rather than actual differences in functionality, perhaps whether to include the implicit/magic `$field`/`$value` variables could be a secondary question(s) on the RFC? 

Also, a small nitpick: The link to your attributeutils repo in the examples page, is broken, and it would be nice to see a few examples showing the explicit version of the hooks.


Cheers

Stephen

---

## #122478 — Larry Garfield — 2024-02-23T15:58:02+00:00

URL: https://externals.io/message/122445#122478 · In reply to: #122477

> > […]

> Hi Larry,
>
> It's good to see this idea still progressing.
>
>
> I have to agree with the other comment(s) that the implicit 
> `$field`/`$value` variables seem odd to me. I understand the desire for 
> brevity and the potential future scope of reused hooks, but this 
> concept seems to fly in the face of many years of PHP reducing "magic" 
> like this.

The "magic" that PHP has been removing is mostly weird and illogical type casting.  As noted, neither of these variables are any more "magic" than $this.

However, since it seems no one likes $field, we have removed it from the RFC.  Of note, to respond to your comment further down, $this->{__PROPERTY__} will not work.  The virtual-property detection looks for the AST representation of $this->propName, and only that.  Dynamic versions that turn into that at runtime cannot work, as it needs to be known at compile time.

For $value, however, we feel strongly that having the default there is a necessary part of the ergonomic picture.  In particular, given your comments here:

> To give one answer to your question about ambiguity if the `$value` 
> parameter is required - I don't believe this is actually ambiguous, in 
> the context of PHP:
> - method parameters in child classes don't implicitly 'inherit' the 
> parent method parameter's type if they don't define one (they widen to 
> mixed);
> - method return types have no implicit inheritance, they must declare a 
> compatible return type;
> - typed class properties don't implicitly inherit the parent type when 
> the type left off a child property - they must declare the same type.
>
> AFAIK there is no existing behaviour in PHP where omitting a type would 
> mean "the type is implicitly inherited from X", it either means the 
> same as mixed, or it's an error.

That to me suggests that IF a custom variable name is provided, we should require also specifying the type.  In which case, in the 95% case, if we require the full argument signature then the 95% case would need to double-specify the type, which is a hard-no from an ergonomic standpoint.

Especially combined with the suggestion yesterday to allow return-to-set in the short-set version, that would mean comparing this:

public string $phone {
    set(string $phone) => $this->phone = $this->sanitizePhone($phone);
}

To this:

public string $phone {
    set => $this->sanitizePhone($value);
}

And to me, there's absolutely no contest.  The latter has about 1/3 as many places for me to make a typo repeating the same information over again.  Now imagine comparing the above in a property that's used with constructor promotion.  

public function __construct(
    public string $phone { set(string $phone) => $this->phone = $this->sanitizePhone($phone); }
    public string $phone { set => $this->sanitizePhone($value); }
) {}

Again, it's absolutely no contest for me.  I would detest writing the longer version every time.

If PHP has been moving away from weird and inexplicable magic, it's also been moving away from needless boilerplate.  (Constructor promotion being the best example, but not the only; types themselves are a factor here, as are arrow functions.)  As the whole point of this RFC is to make writing common code easier, requiring redundant boilerplate for it to work is actively counter-productive.

So what I'd suggest instead is "specify the full signature if you want a custom name OR wider type; or omit the param list entirely to get a same-type $value variable, which 99% of the time is all you need."  We get that in return for documenting "$value is the default", which for someone who has already figured out $this, should be a very low effort to learn.

> Also, a small nitpick: The link to your attributeutils repo in the 
> examples page, is broken, and it would be nice to see a few examples 
> showing the explicit version of the hooks.

Link fixed, thanks.  What do you mean explicit version of the hooks?

--Larry Garfield

---

## #122480 — Marc Bennewitz — 2024-02-23T17:35:19+00:00

URL: https://externals.io/message/122445#122480 · In reply to: #122476

Hi Larry,

first of all thank you very much for this amazing work you two have been 
done :+1:.


> 
> > […]
> For $field, it's not a requirement.  It's mostly for copy-paste convenience.  A number of people have struggled on this point so if the consensus is to leave out $field and just use $this->propName directly, we can accept that.  They can be re-added if reusable hook packages are added in the future (as noted in Future Scope).
>

I'm also feeling that introducing magic variables isn't the best design 
choice.
I read your section about "Why do set hooks not return the value to 
set?" and I don't really agree.

Let me explain ...

1. Virtual properties and technically all functions return a valid.

I think it would me much less magic if property setters on virtual 
properties declare a void return type.
This would make it very obvious that this is a virtual property even on 
having to read complex setters.

2. it disallows any action /after/ the assignment happens

I actually think this would be a good think!

An object property should only be set after everything has been done.
If you want to do more it should either not be part of the setter or you 
should use a temporary variable what you have to do anyway to not leave 
your object in an incorrect state.

Let's say we have the following setter:

public  string$name  {  
     set($value){ // do stuff
         $field = $value; // or $this->name = $value
         // do more stuff and (eventually) fail
         throw Exception();
     }
}

try {
     $object->name = 'test';
} finally {
     $object->name; // what is name here ?
}


3. ambiguity

I actually feel that $field is ambiguous. What happens if you declare 
`set($field) {}` ? Does such construct let the engine set the property 
value immediately as the input value gets immediately assigned to the 
property via $field?


Greetings,
Marc

---

## #122481 — Larry Garfield — 2024-02-23T20:37:58+00:00

URL: https://externals.io/message/122445#122481 · In reply to: #122480

> Hi Larry,
>
> first of all thank you very much for this amazing work you two have been 
> done :+1:.

> I'm also feeling that introducing magic variables isn't the best design 
> choice.
> I read your section about "Why do set hooks not return the value to 
> set?" and I don't really agree.
>
> Let me explain ...
>
> 1. Virtual properties and technically all functions return a valid.
>
> I think it would me much less magic if property setters on virtual 
> properties declare a void return type.
> This would make it very obvious that this is a virtual property even on 
> having to read complex setters.

Making everyone type ": void" after every set hook, when we already know that's going to be the case, seems like a really bad developer experience.

I talked with Ilija extensively about it, and there is no meaningful way to distinguish between "this method returned null" and "this method didn't return" from the call site in the engine.  If we could, that would allow smarter detection of when it makes sense to use a return value.  Hence my suggestion of allowing set-on-return only for the => form.

> 3. ambiguity
>
> I actually feel that $field is ambiguous. What happens if you declare 
> `set($field) {}` ? Does such construct let the engine set the property 
> value immediately as the input value gets immediately assigned to the 
> property via $field?

$field has already been removed.  See previous email.

--Larry Garfield

---

## #122482 — Larry Garfield — 2024-02-23T21:00:26+00:00

URL: https://externals.io/message/122445#122482

> > […]
>
> I guess it's mostly irrelevant on single-use hooks anyway, but that 
> sounds like a potential gotcha with reusable hooks, and I think it's 
> worth making it very clear *now* in the RFC/docs that dynamic access 
> like this won't work as expected (and why). Perhaps some other 
> indicator on reusablele hooks can be used at that point, to signify if 
> it's virtual or not.

Look, we can have a common variable that can be used in all hooks, or we can minimize the "magic" names.  We can't do both.  Make up your mind. :-P

It's already been included in the RFC that only explicit property name usage will work.

As noted, reusable hook "packages" a la Swift is a potential future scope.  At that point having some common variable name is probably sensible, but we can color that bikeshed when we get to it.

> > […]

> > […]
>
> I think you're making slightly misleading comparisons there, by picking 
> the two extremes (and ignoring the possibly for shorter explicit names) 
> - the explicit parameter name surely doesn't preclude the use of 
> return-to-set functionality? So the comparison could equally be:
>
> ```
> public function __construct(
> 	public string $phone { set => $this->sanitizePhone($value); }
> 	public string $phone { set(string $v) => $this->sanitizePhone($v); }
> ){}
>
> ```

Yet nearly all coding standards and recommendations (outside of Go) say to not use single-character variable names, so I wouldn't expect that to be common.  Even if it's not as big of a difference, it's still two places to specify the type and two places to specify the variable name.  That is, two places to get either one wrong.  Constructor Promotion is one of the best features of PHP 8, precisely because it eliminates that kind of duplication.  I see no reason to mandate redundancy and duplication in code.

> > […]
>
> I get your point, and to expand on what I said in the first email - if 
> removing the implicit mode would mean people vote against the RFC, then 
> that's a worse result, IMO (this is why I suggest a secondary vote - 
> perfect is th enemy of good and all that). I personally think the 
> implicit variables will result in less-readable code, but I also know 
> that I'm free to not use the implicit parameter in code that I write, 
> so I trust those with a more accurate finger on the pulse of the voters 
> to know whether the implicit `$value` parameter will help or hinder the 
> RFC to pass.

As has been noted numerous times, there is basically zero way to tell what people will vote before they do unless they explicitly say so on list, and the list has had maybe a half dozen voters comment at most.  So, I truly have no clue if $value is the pass/fail trigger for someone, or which direction.  This is a long-standing issue with the PHP dev process that Internals has shown no desire to address.

The problem with secondary votes is that they come off as "meh, I couldn't be arsed to decide here."  Based on comments on the list, some non-small number of voters see secondary votes as a good "crowdsourcing" answer, and another non-small number of voters see them as abdicating responsibility and want opinionated RFCs.  I have no idea which to go for. :-/

But in this case, the opinionated RFC position says "making people type the same things multiple times on every usage when there's no reason to is silly."

> > […]
>
> I meant hooks that don't use the implicit "magic" variables (i.e. 
> specify the parameter on set() specify the full property name when 
> accessing the backing store). Given your first response I guess this 
> request is a little moot, assuming the examples are updated to remove 
> use of `$field`. Maybe the RFC or an example already covers it and I 
> skipped over it, but it'd be good to make it clear *exactly* what the 
> equivalent of an implicit `$value` parameter is. 

Once others weigh in on either of the proposals I've made in this thread, I can update the RFC accordingly.

--Larry Garfield

---

## #122491 — Rowan Tommins [IMSoP] — 2024-02-25T22:16:28+00:00

URL: https://externals.io/message/122445#122491

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

> 
> > […]
>
>
> 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 { &get => $this->k }
> public mixed $l { &get => $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 "&get" 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 &get 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 {
>          &get { $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, it's confusing if you 
specify a name without a type; so we need to make the name optional as 
well... Compare to C#, where "value" is not a default, it's an 
unchangeable keyword; or Kotlin, where naming it is mandatory but 
doesn't have mention type.


I think my concerns about distinguishing "virtual properties" may stem 
from a similar cause.

In C#, all "properties" are virtual - as soon as you have any 
non-default "get", "set" or "init" definition, it's up to you to declare 
a separate "field" to store the value in. Swift's "computed properties" 
are similar: if you have a custom getter or setter, there is no backing 
store; to add behaviour to a "stored property", you use the separate 
"property observer" hooks.

Kotlin's approach is philosophically the opposite: there are no fields, 
only properties, but properties can access a hidden "backing field" via 
the special keyword "field". Importantly, omitting the setter doesn't 
make the property read-only, it implies set(value) { field = value }

The current RFC attempts to combine all of these ideas into one syntax, 
on top of everything the language already has. The result has some 
odd-shaped corners. For instance, this won't work:

public string $name { set => throw new Exception('Read-only property ' . 
__PROPERTY__); }

But this will:

public string $name { set => throw new Exception('Read-only property ' . 
__PROPERTY__ . '; current value is: ' . $this->name); }

The first declares a virtual property, with no default getter, like in 
C# or Swift. The second instead acts like Kotlin, and has a default 
getter referencing the implicit backing field.

It would be clearer to choose one style or the other: explicitly enable 
the defaults...

public string $name { get; set => throw new Exception('Read-only 
property ' . __PROPERTY__); }    // default getter and backing field 
requested
public string $name { get => $this->name ??= $this->generateName(); }    
// setter disabled because it's not mentioned, even though backing field 
is used

...or explicitly disable them:

public string $name { set => throw new Exception('Read-only property ' . 
__PROPERTY__ }    // implied default getter and backing field
public virtual string $name { get => $this->firstName . ' ' . 
$this->lastName; }    // setter disabled because property is declared 
virtual


I think there's some really great functionality in the RFC, and would 
love for it to succeed in some form, but I think it would benefit from 
removing some of the "magic".


Regards,

-- 
Rowan Tommins
[IMSoP]

---

## #122492 — Rowan Tommins [IMSoP] — 2024-02-26T08:53:35+00:00

URL: https://externals.io/message/122445#122492 · In reply to: #122476

> However, I just had a long discussion with Ilija and there is one 
> possibility we could consider: Use the return value only on the 
> shorthand (arrow-function-like) syntax.
>
> So you could do either of these, which would be equivalent:
>
> set {
>   $this->phone = $this->santizePhone($value);
> }
>
> set => $this->santizePhone($value);

Regarding this point, I've realised that the current short-hand set syntax isn't actually any shorter:

set { $this->phone = $this->santizePhone($value); }
set => $this->phone = $this->santizePhone($value);

It also feels weird to say both "the right-hand side must be a valid expression" and "the value of the expression is ignored".

So I think making the short-hand be "expression to assign to the implicit backing field" makes a lot more sense.

Regards,
-- 
Rowan Tommins
[IMSoP]

---

## #122503 — Frederik Bosch — 2024-02-26T19:02:34+00:00

URL: https://externals.io/message/122445#122503 · In reply to: #122478

> 
> > […]
> The "magic" that PHP has been removing is mostly weird and illogical type casting.  As noted, neither of these variables are any more "magic" than $this.
>
> However, since it seems no one likes $field, we have removed it from the RFC.  Of note, to respond to your comment further down, $this->{__PROPERTY__} will not work.  The virtual-property detection looks for the AST representation of $this->propName, and only that.  Dynamic versions that turn into that at runtime cannot work, as it needs to be known at compile time.
>
> For $value, however, we feel strongly that having the default there is a necessary part of the ergonomic picture.  In particular, given your comments here:
>
> > […]
> That to me suggests that IF a custom variable name is provided, we should require also specifying the type.  In which case, in the 95% case, if we require the full argument signature then the 95% case would need to double-specify the type, which is a hard-no from an ergonomic standpoint.
>
> Especially combined with the suggestion yesterday to allow return-to-set in the short-set version, that would mean comparing this:
>
> public string $phone {
>      set(string $phone) => $this->phone = $this->sanitizePhone($phone);
> }
>
> To this:
>
> public string $phone {
>      set => $this->sanitizePhone($value);
> }
>
> And to me, there's absolutely no contest.  The latter has about 1/3 as many places for me to make a typo repeating the same information over again.  Now imagine comparing the above in a property that's used with constructor promotion.
>
> public function __construct(
>      public string $phone { set(string $phone) => $this->phone = $this->sanitizePhone($phone); }
>      public string $phone { set => $this->sanitizePhone($value); }
> ) {}
>
> Again, it's absolutely no contest for me.  I would detest writing the longer version every time.
>
> If PHP has been moving away from weird and inexplicable magic, it's also been moving away from needless boilerplate.  (Constructor promotion being the best example, but not the only; types themselves are a factor here, as are arrow functions.)  As the whole point of this RFC is to make writing common code easier, requiring redundant boilerplate for it to work is actively counter-productive.
>
> So what I'd suggest instead is "specify the full signature if you want a custom name OR wider type; or omit the param list entirely to get a same-type $value variable, which 99% of the time is all you need."  We get that in return for documenting "$value is the default", which for someone who has already figured out $this, should be a very low effort to learn.
>
> > […]
> Link fixed, thanks.  What do you mean explicit version of the hooks?
>
> --Larry Garfield

Hi Larry,

Great to see you pick up on this proposal again. Rather than "just use 
$this->propName directly", you might want to consider yield to commit 
the property value, as a solution to "to assign and confirm the property 
write while still allowing code to run after it". But then return would 
be also be a logical consequence.

     public  string$propName  {
         set($value)  {  
             yield $value;
             $this->doSomething();
             yield $value;
             return $this->doSomethingElse(); // final commit, not required
         }
     }

If I read your remarks on why not using return I do not get why this 
proposal wants to deviate from a normal function syntax.

function test(string $name) {
     return [$first, $last] = explode(' ', $name);
}

var_dump(test("Larry Garfield")); // returns ["Larry", "Garfield"]

That's how it always has been, no? So in your example, short code 
abbreviated form would not work. One has to write a block.

     public  string$fullName  {  
         set=>  [$this->first,  $this->last]  =  explode  <http://www.php.net/explode>(' ',  \ucfirst  <http://www.php.net/ucfirst>($value));  // error, $fullName is a string, returning array
     }
  
     public  string$fullName  {  
         set{
             [$this->first,  $this->last]  =  explode  <http://www.php.net/explode>(' ',  \ucfirst  <http://www.php.net/ucfirst>($value));  // no error, not returning
         }
     }

Hope this RFC will develop into something that will pass. Good luck!

Regards,
Frederik

---

## #122504 — Rowan Tommins [IMSoP] — 2024-02-26T19:39:07+00:00

URL: https://externals.io/message/122445#122504 · In reply to: #122503

>
> That's how it always has been, no? So in your example, short code 
> abbreviated form would not work. One has to write a block.
>
>      public  string$fullName  {  
>          set=>  [$this->first,  $this->last]  =  explode  <http://www.php.net/explode>(' ',  \ucfirst  <http://www.php.net/ucfirst>($value));  // error, $fullName is a string, returning array
>      }
>   
>      public  string$fullName  {  
>          set{
>              [$this->first,  $this->last]  =  explode  <http://www.php.net/explode>(' ',  \ucfirst  <http://www.php.net/ucfirst>($value));  // no error, not returning
>          }
>      }


I think the intention is that both the block and the arrow syntax would 
have any return value ignored, as happens with constructors, for 
example. Note that in PHP, there is actually no such thing as "a 
function not returning a value", even a "void" function actually returns 
null; so if the return value was treated as meaningful, your second 
example would give an error "cannot assign null to property of type string".

However, as noted in a previous message, I agree that the short form 
meaning "the value returned is saved to the backing field" is both more 
expected and more useful.

The "yield" idea is ... interesting. I think personally I find it a bit 
too magic, and too cryptic to be more readable than an explicit 
assignment. Opinions may vary, though.

Regards,

-- 
Rowan Tommins
[IMSoP]

---

## #122507 — Frederik Bosch — 2024-02-26T20:21:31+00:00

URL: https://externals.io/message/122445#122507 · In reply to: #122504

Hi Rowan,

Thanks for clearing up that the return value will be ignored. I 
understand better why that is (void = null). I do like the updated RFC 
better than the one with the $field variable to write to. My yield 
suggestion was an idea derived from that earlier $field write proposal, 
but I wanted to share it anyhow.

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. I would rather have a $field in get 
referring to the current value, and a $value in set referring to the 
passed value. So with the full name example:

     public string $fullName {
         get ($field): string => $field;

         set ($value): string {
             [$this->first, $this->last] = explode(' ', $value);
             return $value;
         }
     }

I think it would make absolutely clear that the backing value is only 
accessible in the local get/set scope. 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. So with a slightly different full 
name example:

     public string $fullName {
         get ($field): string => $this->first . ' ' . $this->last;

         set ($value): void {
             [$this->first, $this->last] = explode(' ', $value); // real 
void, no value returned
         }
     }

And why yield is magic, I do not get that. The word and the expression 
actually expresses that something is, well, yielded. yield is a word 
that is reserved in the language that serves the purpose of the problem. 
I would use it. It is even explainable that the set function is treated 
as a generator function.

Regards,
Frederik



> 
> > […]
>
>
> I think the intention is that both the block and the arrow syntax 
> would have any return value ignored, as happens with constructors, for 
> example. Note that in PHP, there is actually no such thing as "a 
> function not returning a value", even a "void" function actually 
> returns null; so if the return value was treated as meaningful, your 
> second example would give an error "cannot assign null to property of 
> type string".
>
> However, as noted in a previous message, I agree that the short form 
> meaning "the value returned is saved to the backing field" is both 
> more expected and more useful.
>
> The "yield" idea is ... interesting. I think personally I find it a 
> bit too magic, and too cryptic to be more readable than an explicit 
> assignment. Opinions may vary, though.
>
> Regards,
>
-- 


    Frederik Bosch


      Partner

Genkgo logo
Mail: f.bosch@genkgo.nl <mailto:f.bosch@genkgo.nl>
Web: support.genkgo.com <https://support.genkgo.com>

Entrada 123
Amsterdam
+31 20 244 1920

Genkgo B.V. staat geregistreerd bij de Kamer van Koophandel onder nummer 
56501153

---

## #122508 — Larry Garfield — 2024-02-26T20:24:24+00:00

URL: https://externals.io/message/122445#122508 · In reply to: #122504

> 
> > […]
>
>
> I think the intention is that both the block and the arrow syntax would 
> have any return value ignored, as happens with constructors, for 
> example. Note that in PHP, there is actually no such thing as "a 
> function not returning a value", even a "void" function actually returns 
> null; so if the return value was treated as meaningful, your second 
> example would give an error "cannot assign null to property of type string".

This correct.  Given a function test():

$ret = test('Larry Garfield');

There's no way to tell if $ret is a possibly-null value we should do something with, or null by side-effect.  The RFC right now takes the stance of "it's null by side effect, always, so we never do anything with the return so it's consistent."

> However, as noted in a previous message, I agree that the short form 
> meaning "the value returned is saved to the backing field" is both more 
> expected and more useful.

You're the first person to comment on it, but I'm glad you agree. :-)  I like it, but Ilija is still unsure about it.

> The "yield" idea is ... interesting. I think personally I find it a bit 
> too magic, and too cryptic to be more readable than an explicit 
> assignment. Opinions may vary, though.
>
> Regards,

Mixing in syntax only used for generators here seems like it's asking for trouble.  It wouldn't actually be a coroutine, so using coroutine like syntax would just be confusing.  It's confusing to me whether this implies the hook becomes a generator or not, which means it's likely to confuse a lot of other people.

--Larry Garfield

---

## #122509 — Rowan Tommins [IMSoP] — 2024-02-26T22:46:13+00:00

URL: https://externals.io/message/122445#122509 · In reply to: #122507

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



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


> 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 ="

I would file it with the type widening in the RFC: seems kind of cool, 
but probably isn't worth the added complexity.


Regards,

-- 
Rowan Tommins
[IMSoP]

---

## #122510 — Frederik Bosch — 2024-02-26T23:11:16+00:00

URL: https://externals.io/message/122445#122510 · In reply to: #122509

Hi Rowan,


> 
> > […]
>
>
> 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).

>
> > […]
>
> 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
>
>
> > […]
>
> 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());
}


>
> 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 <mailto:f.bosch@genkgo.nl>
Web: support.genkgo.com <https://support.genkgo.com>

Entrada 123
Amsterdam
+31 20 244 1920

Genkgo B.V. staat geregistreerd bij de Kamer van Koophandel onder nummer 
56501153

---

Next page: https://externals.io/message/122445.md?page=2
