# [RFC] Property hooks, nee accessors

- Thread: https://externals.io/message/120213
- Original archive: https://news-web.php.net/php.internals/120213
- Started by Larry Garfield on 2023-05-08T21:38:07+00:00
- Messages: 33
- RFC: Property access hooks (Implemented (PHP 8.4)) — https://externals.io/rfc/property-hooks.md
- Page 1 of 2: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/120213.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

---

## #120213 — Larry Garfield — 2023-05-08T21:38:07+00:00

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

Ilija Tovilo and I would like to offer another RFC for your consideration.  It's been a while in coming, and we've evolved the design quite a bit just in the last week so if you saw an earlier draft of it in the past few months, I would encourage you to read it over again to make sure we're all on the same page.  I'm actually pretty happy with where it ended up, even if it's not the original design.  This approach eliminates several hard-to-implement edge cases while still providing a lot of functionality in one package.

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

-- 
  Larry Garfield
  larry@garfieldtech.com

---

## #120215 — michal.brzuchalski@gmail.com — 2023-05-09T07:39:44+00:00

URL: https://externals.io/message/120213#120215 · In reply to: #120213

Hi Larry,

pon., 8 maj 2023 o 23:38 Larry Garfield <larry@garfieldtech.com> napisał(a):

> Ilija Tovilo and I would like to offer another RFC for your
> consideration.  It's been a while in coming, and we've evolved the design
> quite a bit just in the last week so if you saw an earlier draft of it in
> the past few months, I would encourage you to read it over again to make
> sure we're all on the same page.  I'm actually pretty happy with where it
> ended up, even if it's not the original design.  This approach eliminates
> several hard-to-implement edge cases while still providing a lot of
> functionality in one package.
>
> https://wiki.php.net/rfc/property-hooks


> Using $this->propertyName directly is supported, but not recommended.
Why is that? IMHO in the scope of accessor this is much clearer to me than
using the $field.

> The following example does make use of $field, however, and thus a
backing value will be created, and write operations will simply write to
the property as normal.
This looks like new magic to me, whether we allow setting and baking the
value explicitly or not. I consider this behavior confusing.

Overall I vote yes.
Cheers,
Michał Marcin Brzuchalski

---

## #120216 — tim@bastelstu.be — 2023-05-09T07:57:26+00:00

URL: https://externals.io/message/120213#120216 · In reply to: #120213

Hi


> Ilija Tovilo and I would like to offer another RFC for your consideration.  It's been a while in coming, and we've evolved the design quite a bit just in the last week so if you saw an earlier draft of it in the past few months, I would encourage you to read it over again to make sure we're all on the same page.  I'm actually pretty happy with where it ended up, even if it's not the original design.  This approach eliminates several hard-to-implement edge cases while still providing a lot of functionality in one package.
> 
> https://wiki.php.net/rfc/property-hooks
> 

I've read a previous draft and now did another pass through the updated 
version:

(1) How does the set() hook interact with #[\SensitiveParameter]?
(2) How will a Stack Trace emitted from a hook look like? Please include 
an example of the Exception::__toString() output or so.
(3) ReflectionProperty::getHook(): Will this throw if an invalid hook 
name is given or will this return null?
(4) The "Unaffected PHP Functionality" section is empty. It should 
either be filled in or removed.
(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.

Best regards
Tim Düsterhus

---

## #120217 — Nicolas Grekas — 2023-05-09T09:54:16+00:00

URL: https://externals.io/message/120213#120217 · In reply to: #120213

Ilija Tovilo and I would like to offer another RFC for your consideration.
> It's been a while in coming, and we've evolved the design quite a bit just
> in the last week so if you saw an earlier draft of it in the past few
> months, I would encourage you to read it over again to make sure we're all
> on the same page.  I'm actually pretty happy with where it ended up, even
> if it's not the original design.  This approach eliminates several
> hard-to-implement edge cases while still providing a lot of functionality
> in one package.
>
> https://wiki.php.net/rfc/property-hooks
>

Congrats for the RFC for property hooks, I like it all, including
abbreviated forms, $value as default name, etc.

Some notes:

   - does set($value) { mean set(mixed $value) { or set(TypeFromProp
   $value) { ? I understand it would be the latter but it might be nice to
   clarify (unless I missed it)
   - function setFullName => ucfirst is not found in the "set" version
   - could parent::$x::set($x) be replaced by parent::$x = $x ? Same for
   the get variant? Why not if not?
   - readonly: could this be supported by materializing them, using the
   storage for memoization after first access? that'd be quite powerful
   - I'm not sure we need ReflectionProperty::getHook('set')  when we can
   do getHooks()['set'] ?? null
   - What does ReflectionMethod::getName return on a hook? And getClosure?
   - Should we add ReflectionProperty::IS_VIRTUAL ?
   - I'm a bit surprised by the possibility of adding attributes to hooks.
   I'm not sure I see the use case and I think this might be confusing in
   addition to attributes on the property itself.
   - For serialization: exclude them from `var_export()` and `serialize()`,
   which don't need virtual props to rebuild the state (you mean
   `json_encode()`, not JsonSerializable on that list), call get for
   var_dump/print_r when it's defined (but ignore exceptions). But (array) /
   `get_mangled_object_vars()` / `get_object_vars()` shouldn't call any "get".

I have a bigger concern: the take on references contradicts with the intro
about BC breaks: turning a materialized property into virtual one would
break BC as far as refs are concerned. One idea to fix that: add a ref
hook, that must return a reference. If not implemented => Error when trying
to get-by-ref. This could also help solve the $foo->array[] case (including
the asym-visiblity issue).

Nicolas

---

## #120218 — Dik Takken — 2023-05-09T11:52:12+00:00

URL: https://externals.io/message/120213#120218 · In reply to: #120213

> Ilija Tovilo and I would like to offer another RFC for your consideration.  It's been a while in coming, and we've evolved the design quite a bit just in the last week so if you saw an earlier draft of it in the past few months, I would encourage you to read it over again to make sure we're all on the same page.  I'm actually pretty happy with where it ended up, even if it's not the original design.  This approach eliminates several hard-to-implement edge cases while still providing a lot of functionality in one package.
> 
> https://wiki.php.net/rfc/property-hooks
> 

Thanks for all the work that has gone into this. It looks great. The 
ability to add properties to interfaces is also really nice.

One thing that concerns me is the following: "the use of [] on any 
property (with or without a key) will result in a runtime error being 
thrown". While the intent of the RFC is to allow adding hooks without 
causing BC breaks, this detail does introduce a BC break. And the break 
may get introduced without realizing it.

I'm not sure which solution would be best here: Just accept the 
limitations for arrays (same as for list properties in Python) or forbid 
the use of hooks for array properties? I'm leaning towards accepting the 
limitations, like Python does. Then it is up to the API designer to 
decide if a property hook is the right tool for the job or not.

Regards,
Dik

---

## #120219 — kontakt@beberlei.de — 2023-05-09T12:38:56+00:00

URL: https://externals.io/message/120213#120219 · In reply to: #120213

On Mon, May 8, 2023 at 11:38 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> Ilija Tovilo and I would like to offer another RFC for your
> consideration.  It's been a while in coming, and we've evolved the design
> quite a bit just in the last week so if you saw an earlier draft of it in
> the past few months, I would encourage you to read it over again to make
> sure we're all on the same page.  I'm actually pretty happy with where it
> ended up, even if it's not the original design.  This approach eliminates
> several hard-to-implement edge cases while still providing a lot of
> functionality in one package.
>
> https://wiki.php.net/rfc/property-hooks


Thank you! Looks interesting, I will need to think about things before more
qualitative feedback.

An error maybe, the "class C" example in "Detailed Proposal > set" uses a
"$this->_prop", but probably meant to use $this->_names, since private
array $_names; is declared in the example.

I think $field should be its own "chapter". Its a central part of the
proposal that should be clarified early and so that readers don't
accidentally skip it.

I am also confused why $field exists when $this->propertyName works and why
its not recommended to be used. Is $field a reference? or does the "compile
time macro" part mean its replaced at compile time? Ifso, this feels
different to anything else PHP, i am leaning towards $this->propertyName if
there are no other compelling reasons why $field should be used.

>
>
> --
>   Larry Garfield
>   larry@garfieldtech.com
>
> --
> 
> To unsubscribe, visit: https://www.php.net/unsub.php

---

## #120220 — Erick de Azevedo Lima — 2023-05-09T12:39:15+00:00

URL: https://externals.io/message/120213#120220 · In reply to: #120218

It looks great!

Thank you so, so much. I've been wanting this for a long time. I have used
properties in C# and it's very useful. I'm reading this RFC since it was in
draft. =)
One thing that I did not understand was this piece of code:

class C {
    public array $_names;
    public string $names {
        set {
            $this->*_prop* = explode(',', $value); // *<---- Where this
"_prop" is coming from?*
        }
    }
}
$c = new C();
var_dump($c->names = 'Ilija,Larry'); // 'Ilija,Larry'
var_dump($c->_names); // ['Ilija', 'Larry']

Does "_prop" have any special meaning or is it just a typo?

Thanks one more time for this!

Regards,
Erick

Em ter., 9 de mai. de 2023 às 08:52, Dik Takken <dik.takken@gmail.com>
escreveu:

>
>
> 
> > […]
> consideration.  It's been a while in coming, and we've evolved the design
> quite a bit just in the last week so if you saw an earlier draft of it in
> the past few months, I would encourage you to read it over again to make
> sure we're all on the same page.  I'm actually pretty happy with where it
> ended up, even if it's not the original design.  This approach eliminates
> several hard-to-implement edge cases while still providing a lot of
> functionality in one package.
> > […]
>
> Thanks for all the work that has gone into this. It looks great. The
> ability to add properties to interfaces is also really nice.
>
> One thing that concerns me is the following: "the use of [] on any
> property (with or without a key) will result in a runtime error being
> thrown". While the intent of the RFC is to allow adding hooks without
> causing BC breaks, this detail does introduce a BC break. And the break
> may get introduced without realizing it.
>
> I'm not sure which solution would be best here: Just accept the
> limitations for arrays (same as for list properties in Python) or forbid
> the use of hooks for array properties? I'm leaning towards accepting the
> limitations, like Python does. Then it is up to the API designer to
> decide if a property hook is the right tool for the job or not.
>
> Regards,
> Dik
>
> --
> 
> To unsubscribe, visit: https://www.php.net/unsub.php

---

## #120221 — Jeffrey Dafoe — 2023-05-09T13:22:01+00:00

URL: https://externals.io/message/120213#120221 · In reply to: #120213

> Ilija Tovilo and I would like to offer another RFC for your consideration.  It's
> been a while in coming, and we've evolved the design quite a bit just in the last
> week so if you saw an earlier draft of it in the past few months, I would
> encourage you to read it over again to make sure we're all on the same page.
> I'm actually pretty happy with where it ended up, even if it's not the original
> design.  This approach eliminates several hard-to-implement edge cases while
> still providing a lot of functionality in one package.
> 
> https://wiki.php.net/rfc/property-hooks

+1 from me, I've used it in C# and it makes for clean code and less boilerplate.

-Jeff

---

## #120222 — Larry Garfield — 2023-05-09T17:46:50+00:00

URL: https://externals.io/message/120213#120222 · In reply to: #120213

> Ilija Tovilo and I would like to offer another RFC for your 
> consideration.  It's been a while in coming, and we've evolved the 
> design quite a bit just in the last week so if you saw an earlier draft 
> of it in the past few months, I would encourage you to read it over 
> again to make sure we're all on the same page.  I'm actually pretty 
> happy with where it ended up, even if it's not the original design.  
> This approach eliminates several hard-to-implement edge cases while 
> still providing a lot of functionality in one package.
>
> https://wiki.php.net/rfc/property-hooks

Thanks everyone for your feedback so far.  I'm going to collapse the replies into one message to reduce noise.



> (1) How does the set() hook interact with #[\SensitiveParameter]?

Internally, the hook is a method, so it should respect it the same way as any other method.  Viz, you should be able to write:

```
public string $foo {
  set ($[\SensitiveParameter] string $value) {
    $field = strtolower($foo);
  }
}
```

I'll ask Ilija to add a test to confirm that.

> (2) How will a Stack Trace emitted from a hook look like? Please include 
> an example of the Exception::__toString() output or so.

I'll ask Ilija to add a test to his branch and link it here.

> (3) ReflectionProperty::getHook(): Will this throw if an invalid hook 
> name is given or will this return null?

Null if you ask for get/set and one doesn't exist.  Error if you ask for something other than "get" or "set".  We're still keeping it as a single method, though, to make supporting other hooks easier in the future if desired.  I've updated the RFC to clarify that.

> (4) The "Unaffected PHP Functionality" section is empty. It should 
> either be filled in or removed.

Removed.  I don't quite understand the purpose of that section. :-)



>    - does set($value) { mean set(mixed $value) { or set(TypeFromProp
>    $value) { ? I understand it would be the latter but it might be nice to
>    clarify (unless I missed it)

Conceptually, I believe it means set(TypeFromProp $value).  Since at present that cannot be enforced, though, they're basically the same thing.  (If someone with more engine-fu than either of us wants to help enforce contravariant types in set, please speak up because we'd prefer to do so.)

>    - function setFullName => ucfirst is not found in the "set" version

Yes it is.  Look at the end of the `set` line.

>    - could parent::$x::set($x) be replaced by parent::$x = $x ? Same for
>    the get variant? Why not if not?

There's a subtle but important distinction here.  parent::$x would suggest accessing the parent *property*, ie, its backing value, directly.  What is actually intended here is accessing the parent *hook*, ie, its equivalent to parent::setFoo($foo).  The property itself is already available as $field.

We're open to alternate syntaxes for accessing the parent accessor syntax if people have good (and easily implementable) suggestions.

>    - readonly: could this be supported by materializing them, using the
>    storage for memoization after first access? that'd be quite powerful

That was one of my early ideas, as it then became a cheap form of cached lazy property.  However, that then means there's no meaningful way to unset the cache.  In the end we decided that there were too many nooks and crannies to readonly to try and make sense of.  (The more I use it, the more I think readonly was a mistake and we should have gone straight to aviz.)

(As a side note, if we had aviz, then we could use the field itself as a cache by making writes private, like so:

public string $name => $field ??= $this->first . $this->last;

But right now that would also imply public-set, which we wouldn't want.  Aviz would solve that issue entirely.  Another reason aviz is an important and useful feature. :-) )

What a readonly flag on a set-only property means... I have no idea.  

>    - I'm not sure we need ReflectionProperty::getHook('set')  when we can
>    do getHooks()['set'] ?? null

The double-methods were mainly for consistency with the rest of the Reflection API, which tends to have both getFoo(): object and getFoos(): array options.  It's no more redundant than the rest of Reflection.

>    - What does ReflectionMethod::getName return on a hook? And getClosure?

Either "get" or some hashed string that includes "get".  I'm not sure off hand.  I'll have to check with Ilija.

>    - Should we add ReflectionProperty::IS_VIRTUAL ?

Funny story.  I just asked Ilija this, and he said he already did add it for testing purpose but forgot to tell me. :-)  RFC updated.

>    - I'm a bit surprised by the possibility of adding attributes to hooks.
>    I'm not sure I see the use case and I think this might be confusing in
>    addition to attributes on the property itself.

I think in practice it's actually more work not to, since hooks under the hood are just methods like anything else.  And as Tim noted above, there are use cases like #[SensitiveParameter] for attributes even down here.

>    - For serialization: exclude them from `var_export()` and `serialize()`,
>    which don't need virtual props to rebuild the state (you mean
>    `json_encode()`, not JsonSerializable on that list), call get for
>    var_dump/print_r when it's defined (but ignore exceptions). But (array) /
>    `get_mangled_object_vars()` / `get_object_vars()` shouldn't call any "get".

I don't follow.  `json_encode()` is on the list.

> I have a bigger concern: the take on references contradicts with the intro
> about BC breaks: turning a materialized property into virtual one would
> break BC as far as refs are concerned. One idea to fix that: add a ref
> hook, that must return a reference. If not implemented => Error when trying
> to get-by-ref. This could also help solve the $foo->array[] case (including
> the asym-visiblity issue).

That might be possible.  The issue there is that the returned ref would still be bypassing the get/set hooks, so you're just adding a way to dance around the existing hook implementations, which is exactly what we're trying to avoid.  Also, what would that hook even mean if used on a virtual property?

Moreover, in practice, getting a reference to a property is extremely rare, with the exception of array writes.  I cannot recall the last time I even saw some code get a reference to a property.  That's why we felt comfortable just leaving it at is.  In an earlier version of the RFC we had a way for properties to just disable references without adding any hooks, but removed it on the grounds that it was not worth the effort.



> I'm not sure which solution would be best here: Just accept the 
> limitations for arrays (same as for list properties in Python) or forbid 
> the use of hooks for array properties? I'm leaning towards accepting the 
> limitations, like Python does. Then it is up to the API designer to 
> decide if a property hook is the right tool for the job or not.

We considered disallowing it on array properties; the problem is, there's also mixed and iterable properties that would have the same challenge, but that couldn't be detected at compile time.  That means the runtime check has to be there anyway, so also having a compile-time check for hooks on arrays doesn't really buy us anything but more engine code.  So yes, Python-style "just deal with it *sunglasses*" is the most logical approach.



> An error maybe, the "class C" example in "Detailed Proposal > set" uses a
> "$this->_prop", but probably meant to use $this->_names, since private
> array $_names; is declared in the example.

Yep, typo, thanks.  Fixed now.

> I think $field should be its own "chapter". Its a central part of the
> proposal that should be clarified early and so that readers don't
> accidentally skip it.
>
> I am also confused why $field exists when $this->propertyName works and why
> its not recommended to be used. Is $field a reference? or does the "compile
> time macro" part mean its replaced at compile time? Ifso, this feels
> different to anything else PHP, i am leaning towards $this->propertyName if
> there are no other compelling reasons why $field should be used.

Regarding $field vs. $this->propName, there's a few reasons we went that route.

1. It's shorter and less typing for what will be a common pattern.
2. That makes it consistent between hook implementations.  In working on examples, I found many cases where I was adding basically the same line to multiple hooks on the same class.  Making the code easier to copy-paste seems like a win.
3. It also will be helpful if hook packages are added in the future, as they'll need a more "generic" way to access the backing property.  (See Future Scope.)  Eg, "$field = someLogic($value)" applied to a dozen different properties; it wouldn't work if it was "$this->specificProperty = someLogic($value)".
4. We're used to our eyes glossing over "$this->propName", as it's so common.  Having a separate name to mentally scan for to determine if a property is virtual or not seems like it will be helpful in practice.
5. There's precedent for it: Kotlin has almost the same functionality as we describe here, and uses a `field` variable in the exact same way.

So it's mainly an ergonomics argument rather than a technical one.  "Compile time macro" means it translates to the same AST as if you'd used $this->propName.  There's precedent for that.  Constructor Property Promotion works basically the same way.

--Larry Garfield

---

## #120223 — Larry Garfield — 2023-05-09T22:59:43+00:00

URL: https://externals.io/message/120213#120223 · In reply to: #120222

> > […]
>
> That might be possible.  The issue there is that the returned ref would 
> still be bypassing the get/set hooks, so you're just adding a way to 
> dance around the existing hook implementations, which is exactly what 
> we're trying to avoid.  Also, what would that hook even mean if used on 
> a virtual property?
>
> Moreover, in practice, getting a reference to a property is extremely 
> rare, with the exception of array writes.  I cannot recall the last 
> time I even saw some code get a reference to a property.  That's why we 
> felt comfortable just leaving it at is.  In an earlier version of the 
> RFC we had a way for properties to just disable references without 
> adding any hooks, but removed it on the grounds that it was not worth 
> the effort.

Addendum here: The RFC wasn't clear, in part because I wasn't clear, but the prohibition on [] on properties applies only on set.  On get, whatever array value is returned from the hook is just a boring array, which can then be dereferenced to look up a key.

I've updated the RFC accordingly to make it clearer what is and isn't allowed with arrays.

--Larry Garfield

---

## #120224 — Robert Landers — 2023-05-10T11:35:24+00:00

URL: https://externals.io/message/120213#120224 · In reply to: #120222

> Regarding $field vs. $this->propName, there's a few reasons we went that route.
>
> 1. It's shorter and less typing for what will be a common pattern.
> 2. That makes it consistent between hook implementations.  In working on examples, I found many cases where I was adding basically the same line to multiple hooks on the same class.  Making the code easier to copy-paste seems like a win.
> 3. It also will be helpful if hook packages are added in the future, as they'll need a more "generic" way to access the backing property.  (See Future Scope.)  Eg, "$field = someLogic($value)" applied to a dozen different properties; it wouldn't work if it was "$this->specificProperty = someLogic($value)".
> 4. We're used to our eyes glossing over "$this->propName", as it's so common.  Having a separate name to mentally scan for to determine if a property is virtual or not seems like it will be helpful in practice.
> 5. There's precedent for it: Kotlin has almost the same functionality as we describe here, and uses a `field` variable in the exact same way.
>
> So it's mainly an ergonomics argument rather than a technical one.  "Compile time macro" means it translates to the same AST as if you'd used $this->propName.  There's precedent for that.  Constructor Property Promotion works basically the same way.

With using a common name for say, $value, open the door for a library
of common hook implementations (eventually)?

Maybe something like:

class User {
  // snip
  public string $fullName { get => FancyLibrary\fullName(...) }
  // snip
}

I could imagine something like this would be a huge boon to PHP if it
automatically bound the closure.

---

## #120225 — Larry Garfield — 2023-05-10T13:18:50+00:00

URL: https://externals.io/message/120213#120225 · In reply to: #120224

> > […]
>
> With using a common name for say, $value, open the door for a library
> of common hook implementations (eventually)?
>
> Maybe something like:
>
> class User {
>   // snip
>   public string $fullName { get => FancyLibrary\fullName(...) }
>   // snip
> }
>
> I could imagine something like this would be a huge boon to PHP if it
> automatically bound the closure.

$field is only available inside the hook itself, not inside functions called from it.  But that does mean you could do this instead:

class User {
  // snip
  public string $fullName { get => FancyLibrary\fullName($field) }
  // snip
}

Which, yes, should work fine.

If such a library actually became popular, that would be an argument to implement the "property hook packages" or "property traits" concept that Swift has, as noted in Future Scope.

--Larry Garfield

---

## #120267 — Matthew Brown — 2023-05-14T02:52:57+00:00

URL: https://externals.io/message/120213#120267 · In reply to: #120222

>
> Regarding $field vs. $this->propName, there's a few reasons we went that
> route.


Overall I think this is a really good proposal, but you might want to
consider a second vote for that particular syntax.

`$field` vs `$this->propName` feels a little magical. It's a simpler magic
than actual magic methods — it's magic that static analysis tools can
quickly reason about (as they can with promoted properties).

But I can imagine developers coming across this particular syntax in a PR
and thinking "that looks like a bug".

I know it's a few extra keypresses, but I think $this->propName is easier
to scan, and more familiar (given this RFC introduces a lot of other new
syntax).

---

## #120276 — Hendra Gunawan — 2023-05-14T23:41:12+00:00

URL: https://externals.io/message/120213#120276 · In reply to: #120213

>
> Ilija Tovilo and I would like to offer another RFC for your consideration.  It's been a while in coming, and we've evolved the design quite a bit just in the last week so if you saw an earlier draft of it in the past few months, I would encourage you to read it over again to make sure we're all on the same page.  I'm actually pretty happy with where it ended up, even if it's not the original design.  This approach eliminates several hard-to-implement edge cases while still providing a lot of functionality in one package.
>
> https://wiki.php.net/rfc/property-hooks
>
> --
>   Larry Garfield
>   larry@garfieldtech.com
>
> --
> 
> To unsubscribe, visit: https://www.php.net/unsub.php
>

Hi Larry.

```
public string $fullName => $this->first . " " . $this->last;
```
1. Suppose that I forgot to declare ```$this->first```. Based on the
"deprecate dynamic properties" proposal, will I get an error/warning?

2. The shorthand notations supported (the shortest one) creates
impaired syntax, and not pretty to look at for constructor property
promotion.

```
public function __construct(
  public string $prop1 => strtoupper($this->_prop1),
  public string $prop2 {set => $field = strtolower($value);},
){}
```

My suggestion is: use get/set keyword immediately after property
name/default value rather than "=>" or "{" without losing multiline
statements.
```
public function __construct(
  public string $prop1 get => strtoupper($this->_prop1),
  public string $prop2 set => $field = strtolower($value),
  public string $prop3 get {
    $temp = strtoupper($this->_prop1);
    return substr($temp, 0, 10) . '...';
  }
  public string $prop4 set {
    $temp = strtolower($value);
    $field = substr($temp, 0, 10);
  }
){}
```
This syntax is aligned with a single statement if/for/foreach.

---

## #120277 — Hendra Gunawan — 2023-05-15T02:20:56+00:00

URL: https://externals.io/message/120213#120277 · In reply to: #120213

>
> Ilija Tovilo and I would like to offer another RFC for your consideration.  It's been a while in coming, and we've evolved the design quite a bit just in the last week so if you saw an earlier draft of it in the past few months, I would encourage you to read it over again to make sure we're all on the same page.  I'm actually pretty happy with where it ended up, even if it's not the original design.  This approach eliminates several hard-to-implement edge cases while still providing a lot of functionality in one package.
>
> https://wiki.php.net/rfc/property-hooks
>
> --
>   Larry Garfield
>   larry@garfieldtech.com
>
> --
> 
> To unsubscribe, visit: https://www.php.net/unsub.php
>

Hi Larry.

Suppose that I just want to cache once and freeze, and I don't want to
create additional hidden property. Please make a judgement on the
validity of this syntax:
```
public string $prop1 {
  get => $field ??= $this->heavyTask($this->propx, $prop->y);
  set => null;
}
```

Explanation:
* Since the ```get``` hook uses ```$field```, the engine will create a
backing store value with name ```$prop1```, and will be filled at the
first call only. The later call will use the already stored value.
* Since no new value is assigned to ```$field``` in the ```set```
hook, the value stored in ```$prop1``` is still the same as before.
* Since any return statement will be discharged in ```set``` hook, the
assignment chaining is safe to use and the value which is transferred
is alway the rightmost value (not the value returned by the ```set```
hook).
```
$varx = $obj->prop1 = "hello";  // $varx value is "hello", and
$obj->prop1 value is unchanged
```

But, this statement below is NOT ALWAYS correct for "cache once and freeze":
```
public string $prop1 {
  set => $field ??= $this->heavyTask($this->propx, $prop->y);
}
```
Explanation:
* Since default ```get``` hook can be called before ```set``` hook,
backing store value with name ```$prop1``` will still be empty.
* Otherwise, I have to duplicate the body of the ```set``` hook into
```get``` hook to prevent an emptiness backing store.

Please give a comment.
Thanks.

---

## #120278 — Hendra Gunawan — 2023-05-15T05:58:23+00:00

URL: https://externals.io/message/120213#120278 · In reply to: #120213

>
> Ilija Tovilo and I would like to offer another RFC for your consideration.  It's been a while in coming, and we've evolved the design quite a bit just in the last week so if you saw an earlier draft of it in the past few months, I would encourage you to read it over again to make sure we're all on the same page.  I'm actually pretty happy with where it ended up, even if it's not the original design.  This approach eliminates several hard-to-implement edge cases while still providing a lot of functionality in one package.
>
> https://wiki.php.net/rfc/property-hooks
>
> --
>   Larry Garfield
>   larry@garfieldtech.com

Hi, Larry

```
interface IFace
{
  public string $readable { get; }
  public string $writeable { set; }
  public string $both { get; set; }
}
```
How important is ```{ get; }``` statement (and friends) in the interface?

Since PHP does not enforce method definition existence in caller site,
this code is valid (at least at compile time):
```
interface IA {
  // ...
}

interface IB {
  public function doIt(IA $par1);
}

class CA1 implements IA {
  // makeIt is not part of IA
  public function makeIt() {
    echo " makeIt() is called";
  }
}

class CA2 implements IA {}

class CB implements IB {
  public function doit(IA $par1) {
    // makeIt is not enforced to be invalid
    $par1->makeit();
  }
}

$a1 = new CA1;
$a2 = new CA2;
$b = new CB;
$b->doIt($a1); // valid
$b->doIt($a2); // invalid at runtime
```

IMO, ```{ get; }``` (and friends) only useful if there are:
* definition existence enforcement or implemented asymmetric visibility
* AND no default hook implementation provided.

I assume that ```{ get; }``` do exist in the property accessors
proposal because there is no default hook implementation. Whether
there is already implemented property definition existence enforcement
or not, i don't know. If yes, then all things are in the right place.
If not, it feels like a half baked feature, just like the method was:
the error will pop up at runtime. The only positive value is ```{ get;
}``` can still be consumed by static analysis tools.
```
class CX {
  public function consumeIt(IFace $par) {
    // this statement should trigger error
    // produced by engine or SA tools
    $par->readable = "hello";
  }
}
```

Things will be different with property hooks. Without definition
existence enforcement nor implemented asymmetric visibility, static
analysis tools will give misconception about unlisted hooks if there
is error/warning message.
```
class CX {
  public function consumeIt(IFace $par) {
    // default hook is provided by engine
    // any message is not relevant for this valid statement
    $par->readable = "hello";
  }
}
```

If there is no forward compatibility consideration, can we simplify
interface definition to this one?
```
interface IFace
{
  public string $readable;
  public string $writeable;
  public string $both;
}
```

Thanks in advance.

---

## #120279 — Hendra Gunawan — 2023-05-15T08:14:29+00:00

URL: https://externals.io/message/120213#120279 · In reply to: #120213

>
> Ilija Tovilo and I would like to offer another RFC for your consideration.  It's been a while in coming, and we've evolved the design quite a bit just in the last week so if you saw an earlier draft of it in the past few months, I would encourage you to read it over again to make sure we're all on the same page.  I'm actually pretty happy with where it ended up, even if it's not the original design.  This approach eliminates several hard-to-implement edge cases while still providing a lot of functionality in one package.
>
> https://wiki.php.net/rfc/property-hooks
>
> --
>   Larry Garfield
>   larry@garfieldtech.com

Hi Larry

Can the engine detect these logical errors?
```
public string $fullName = "hendra gunawan" => $this->first . " " . $this->last;

// or with my proposal:
public string $fullName = "hendra gunawan" get => $this->first . " " .
$this->last;

// or even this one:
public string $fullName = "hendra gunawan" {
  get => $this->first . " " . $this->last;
  set => [$this->first, $this->last] = explode(" ", $value);
```

Explanation:
There is no automatic backing store value created to store the default value.

Terima Kasih.

---

## #120280 — Lynn — 2023-05-15T08:32:48+00:00

URL: https://externals.io/message/120213#120280 · In reply to: #120276

On Mon, May 15, 2023 at 1:41 AM Hendra Gunawan <the.liquid.metal@gmail.com>
wrote:

> 2. The shorthand notations supported (the shortest one) creates
> impaired syntax, and not pretty to look at for constructor property
> promotion.
>

It's starting to get crowded in object constructors. The following example
is so much more readable and maintainable imo. Would it be possible to
still add the quick assignment as a language feature? I'm personally not
happy having property definitions in both the class body _and_ the
constructor signature.

```php
class User {
    private string $first;
    private string $last;
    public string $fullName {
        get => $this->first . ' ' . $this->last;
        set => [$this->first, $this->last] = explode(' ', $value, 2);
    }

    public function __construct($this->fullName) {}
}

// vs

class User {
    private string $first;
    private string $last;

    public function __construct(
        public string $fullName {
            get => $this->first . ' ' . $this->last;
            set => [$this->first, $this->last] = explode(' ', $value, 2);
        }
    ) {}
}

// or

class User {
    private string $first;
    private string $last;
    public string $fullName {
        get => $this->first . ' ' . $this->last;
        set => [$this->first, $this->last] = explode(' ', $value, 2);
    }

    public function __construct(string $fullName)
    {
        $this->fullName = $fullName;
    }
}

```

---

## #120282 — Hendra Gunawan — 2023-05-15T09:15:42+00:00

URL: https://externals.io/message/120213#120282 · In reply to: #120280

> ```php
> class User {
>     private string $first;
>     private string $last;
>     public string $fullName {
>         get => $this->first . ' ' . $this->last;
>         set => [$this->first, $this->last] = explode(' ', $value, 2);
>     }
>
>     public function __construct($this->fullName) {}
> }
> ```

Not entirely true, you created redundancy code for ```$fullName```.
This is why we created constructor property promotion: to eliminate
redundancy.

---

## #120290 — Hendra Gunawan — 2023-05-15T17:50:09+00:00

URL: https://externals.io/message/120213#120290 · In reply to: #120278

> If there is no forward compatibility consideration, can we simplify
> interface definition to this one?
> ```
> interface IFace
> {
>   public string $readable;
>   public string $writeable;
>   public string $both;
> }
> ```

After carefully reading the proposal again, I think I know the answer
for my own question.

Short answer: no, we still need ```{ get; }``` to enforce the implemented class.

There are two different things here:
1. enforcing the implemented class
2. enforcing the caller site

If there are only ordinary properties, then ```{ get; }``` is truly
useless at the moment. On the other hand, virtual properties need
rules to be enforced, since there is no default hook given to it.

Since PHP lacks method definition enforcement at the caller site, I
assume that property would suffer the same.

Since we don't know what type of property is in the caller site,
static analysis tools can benefit from ```{ get; }``` to produce a
warning that we should not assign value to the property. A more
complex check should be performed to the implemented class because the
property type can be determined directly (if i am not wrong).
```
interface IFace
{
  public string $readable1 { get; }
  public string $readable2 { get; }
}

class CX implements IFace {
  private string $_propx;

  public string $readable1;
  public string $readable2 {
    get => $this->_propx;
    set => $this->_propx = $value;
  }

  public function consumeIt(IFace $par) {
    // these statements should trigger warning by SA tools
    $par->readable1 = "hello";
    $par->readable2 = "world";

    // OK
    $this->readable1 = "hello";

    // this statement should trigger error by SA tools
    $this->readable2 = "hello";
  }
}
```

---

## #120294 — Larry Garfield — 2023-05-15T18:34:19+00:00

URL: https://externals.io/message/120213#120294 · In reply to: #120276

Consolidating all replies into a single message.  Please try to keep them together so it's easier to respond to.

(Also, when replying, please please remove duplicate names from the to/cc line.  I just got double copies of everything in this thread this morning.  I'm on the list, I don't need to be CCed.)



> Hi Larry.
>
> ```
> public string $fullName => $this->first . " " . $this->last;
> ```
> 1. Suppose that I forgot to declare ```$this->first```. Based on the
> "deprecate dynamic properties" proposal, will I get an error/warning?

Correct, it will behave the same as if you had a fullName() method that accessed an undefined $first property.  

> 2. The shorthand notations supported (the shortest one) creates
> impaired syntax, and not pretty to look at for constructor property
> promotion.
>
> ```
> public function __construct(
>   public string $prop1 => strtoupper($this->_prop1),
>   public string $prop2 {set => $field = strtolower($value);},
> ){}
> ```

Well, the first is not really valid anyway.  That creates a virtual property with no set hook, which when you pass a value to it in the constructor call will give an error that you are trying to write to a virtual property.  So that's already a non-issue.

For the second, the problem with omitting the {} is that it creates yet another syntax variant.  That makes it harder for the parser, for static analyzers, for user-space parsing tools like php-parser, etc.  That's more work for everyone for fairly little gain.

Removing the {} entirely from all forms makes it harder to denote where the list of hooks begins and ends.  They would probably have to be comma-separated, like this:

public string $foo 
  get { return $this->bar; }, 
  set { $this->bar = $value; };

Which I'd argue is harder to read anyway, because you have to be careful of the very small , and ; marks.  The {} is just easier for humans to parse as well as the machine.

> Suppose that I just want to cache once and freeze, and I don't want to
> create additional hidden property. Please make a judgement on the
> validity of this syntax:
> ```
> public string $prop1 {
>   get => $field ??= $this->heavyTask($this->propx, $prop->y);
>   set => null;
> }
> ```
>
> Explanation:
> * Since the ```get``` hook uses ```$field```, the engine will create a
> backing store value with name ```$prop1```, and will be filled at the
> first call only. The later call will use the already stored value.
> * Since no new value is assigned to ```$field``` in the ```set```
> hook, the value stored in ```$prop1``` is still the same as before.
> * Since any return statement will be discharged in ```set``` hook, the
> assignment chaining is safe to use and the value which is transferred
> is alway the rightmost value (not the value returned by the ```set```
> hook).
> ```
> $varx = $obj->prop1 = "hello";  // $varx value is "hello", and
> $obj->prop1 value is unchanged
> ```

An interesting approach.  It should work as you describe, but there's two reasons I would discourage it:

1. A "set that doesn't set" creates unexpected behavior for callers.  If I call set, I expect something to happen.  It would be rather confusing to call set and not set things.
2. It doesn't actually allow a cache clearing option.

What we really would need here is asymmetric visibility.  No set hook, but make the property private(set) so that trying to write to it gives an error, as it should.  For cache clearing, the best way to do that is probably with an unset hook.  That's been omitted from the RFC for now for simplicity, but that sounds like it would be an argument to introduce it later.  Asymmetric visibility would be the higher priority, though.

> But, this statement below is NOT ALWAYS correct for "cache once and freeze":
> ```
> public string $prop1 {
>   set => $field ??= $this->heavyTask($this->propx, $prop->y);
> }
> ```
> Explanation:
> * Since default ```get``` hook can be called before ```set``` hook,
> backing store value with name ```$prop1``` will still be empty.
> * Otherwise, I have to duplicate the body of the ```set``` hook into
> ```get``` hook to prevent an emptiness backing store.
>
> Please give a comment.
> Thanks.

Correct; while this syntax would compile, it would result in the value assigned to the property being ignored, and then the other heavyTask() value assigned to it, but that still wouldn't ensure it was available the first time you called `get`.  That's probably not what anyone wants.

It looks like you answered your own question on the interface point, so I'll leave that be.

--Larry Garfield

---

## #120297 — Larry Garfield — 2023-05-15T18:45:37+00:00

URL: https://externals.io/message/120213#120297 · In reply to: #120279

Missed one...



> Can the engine detect these logical errors?
> ```
> public string $fullName = "hendra gunawan" => $this->first . " " . $this->last;
>
> // or with my proposal:
> public string $fullName = "hendra gunawan" get => $this->first . " " .
> $this->last;
>
> // or even this one:
> public string $fullName = "hendra gunawan" {
>   get => $this->first . " " . $this->last;
>   set => [$this->first, $this->last] = explode(" ", $value);
> ```
>
> Explanation:
> There is no automatic backing store value created to store the default value.
>
> Terima Kasih.

Those would be compile-time syntax errors, yes.  (Or possibly link time, if inheritance is involved, but still before execution time.)

--Larry Garfield

---

## #120299 — Larry Garfield — 2023-05-15T18:49:10+00:00

URL: https://externals.io/message/120213#120299 · In reply to: #120280

> It's starting to get crowded in object constructors. The following example
> is so much more readable and maintainable imo. Would it be possible to
> still add the quick assignment as a language feature? I'm personally not
> happy having property definitions in both the class body _and_ the
> constructor signature.
>
> ```php
> class User {
>     private string $first;
>     private string $last;
>     public string $fullName {
>         get => $this->first . ' ' . $this->last;
>         set => [$this->first, $this->last] = explode(' ', $value, 2);
>     }
>
>     public function __construct($this->fullName) {}
> }
>
> // vs
>
> class User {
>     private string $first;
>     private string $last;
>
>     public function __construct(
>         public string $fullName {
>             get => $this->first . ' ' . $this->last;
>             set => [$this->first, $this->last] = explode(' ', $value, 2);
>         }
>     ) {}
> }
>
> // or
>
> class User {
>     private string $first;
>     private string $last;
>     public string $fullName {
>         get => $this->first . ' ' . $this->last;
>         set => [$this->first, $this->last] = explode(' ', $value, 2);
>     }
>
>     public function __construct(string $fullName)
>     {
>         $this->fullName = $fullName;
>     }
> }

The last version skips constructor promotion entirely, so will absolutely work.  For this case, that's frankly the best approach, since you're right, the constructor gets rather messy otherwise.

Changing the syntax and semantics for CPP itself (as in the first code block above) is considerably out of scope for this RFC.  I recall someone pointing out why double-declaring the property would cause problems, but I forget the reason.  In any case, another promotion syntax would be a topic for another RFC.  I don't recall off hand the reasons Nikita had for choosing the syntax that ended up being used.

--Larry Garfield

---

## #120303 — Hendra Gunawan — 2023-05-15T20:12:06+00:00

URL: https://externals.io/message/120213#120303 · In reply to: #120294

> (Also, when replying, please please remove duplicate names from the to/cc line.  I just got double copies of everything in this thread this morning.  I'm on the list, I don't need to be CCed.)

Sorry about that. Not my habit before.

> For the second, the problem with omitting the {} is that it creates yet another syntax variant.  That makes it harder for the parser, for static analyzers, for user-space parsing tools like php-parser, etc.  That's more work for everyone for fairly little gain.

Sad to read that. Making one of them as the shortest shorthand feels
like a picky decision. Some people agree with ```get```. Some people
will argue that ```set``` deserves more than ```get```. Personally, I
choose ```set``` to be the shortest shorthand as it is more relevant
with constructor property promotion and value object. Do we need a 2nd
vote for this one?

> Removing the {} entirely from all forms makes it harder to denote where the list of hooks begins and ends.  They would probably have to be comma-separated, like this:
>
> public string $foo
>   get { return $this->bar; },
>   set { $this->bar = $value; };
>
> Which I'd argue is harder to read anyway, because you have to be careful of the very small , and ; marks.  The {} is just easier for humans to parse as well as the machine.

Looks like it's not more simple compared to the other one.

> What we really would need here is asymmetric visibility.  No set hook, but make the property private(set) so that trying to write to it gives an error, as it should.

So if asymmetric visibility is implemented, will my code be this simple?
```
// cache once and freeze,
// and without additional hidden property
public private(set) string $prop1 {
  get => $field ??= $this->heavyTask($this->propx, $prop->y);
}
```

>  For cache clearing, the best way to do that is probably with an unset hook.  That's been omitted from the RFC for now for simplicity, but that sounds like it would be an argument to introduce it later.

I was going to write about this one. IMO, why do we need to provide
additional hidden properties, when we are able to store the cache
directly into it? Obviously we need a ```unset``` hook.

---

## #120304 — Larry Garfield — 2023-05-15T21:06:25+00:00

URL: https://externals.io/message/120213#120304 · In reply to: #120303

> > […]
>
> Sad to read that. Making one of them as the shortest shorthand feels
> like a picky decision. Some people agree with ```get```. Some people
> will argue that ```set``` deserves more than ```get```. Personally, I
> choose ```set``` to be the shortest shorthand as it is more relevant
> with constructor property promotion and value object. Do we need a 2nd
> vote for this one?

Secondary votes are generally discouraged.  I can see the argument for wanting a short-short version of set, given how common the validation use case is, but => is almost universally the "evaluates to" symbol, so using that for a set operation rather than get just feels weirdly inconsistent.  If there were a set-only shorthand, it should be something else.

That said, the ideal for validation would probably be guard clauses, like this, but that would be a completely different RFC for another time.

function foo(string $name where strlen($name) < 10) { ... }

class Foo {
  public function __construct(
    public string $name where strlen($name) < 10;
  ) {}
}

> > […]
>
> So if asymmetric visibility is implemented, will my code be this simple?
> ```
> // cache once and freeze,
> // and without additional hidden property
> public private(set) string $prop1 {
>   get => $field ??= $this->heavyTask($this->propx, $prop->y);
> }
> ```

If you don't want cache clearing, yes.  Ilija also noted to me off-list that you could also have a set hook that throws an exception rather than just being null, which would also work to prevent writes.

For clearing that cached value, I think an unset hook is probably the best way, with either approach.

> > […]
>
> I was going to write about this one. IMO, why do we need to provide
> additional hidden properties, when we are able to store the cache
> directly into it? Obviously we need a ```unset``` hook.

I think an unset hook is a fine addition in the future.  The way the code is implemented it should be straightforward to add.  However, it feels like scope creep to include it right now, especially when there are workarounds available.  If there's a clear consensus from voters to include it, though, we can consider that.  (Meaning, anyone that would favor including an unset hook now, speak up or we'll assume it's better to stick with just get/set for now.)

--Larry Garfield

---

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