[PHP-DEV] [RFC][Vote announcement] Property hooks

Hi everyone

Heads-up: Larry and I would like to start the vote of the property
hooks RFC tomorrow:
https://wiki.php.net/rfc/property-hooks

We have worked long and hard on this RFC, and hope that we have found
some middle-ground that works for the majority. One last concern we
have not officially clarified on the list:

I personally do not feel strongly about whether asymmetric types make it into the initial implementation. Larry does, however, and I think it is not fair to exclude them without providing any concrete reasons not to. [snip]

My concern is more about the external impact of what is effectively a change to the type system of the language: [snip] will tools like PhpStan and Psalm require complex changes to analyse code using such properties?

In particular, this paragraph is referencing the ability to widen the
accepted $value parameter type of the set hook, described at the
bottom of PHP: rfc:property-hooks. I have talked
to Ondřej Mirtes, the maintainer of PHPStan, and he confirmed that
this should not be complex to implement in PHPStan. In fact, PHPStan
already offers the @property-read and @property-write class
annotations, which can be used to describe "virtual" properties
handled within __get/__set, already providing asymmetric types of
sorts. Hence, this concern should be a non-issue.

Thank you to everybody who has contributed to the discussion!

Ilija

Ilija, Heads-up: I’m still writing up an opinion and intend to send it to the list before end of day (CET). I know I’m late to the party, but I’ve been having trouble finding the words to express myself properly regarding this RFC (and have been struggling to find the right words for months). Smile, Juliette

···

On 8-4-2024 23:39, Ilija Tovilo wrote:

Hi everyone

Heads-up: Larry and I would like to start the vote of the property
hooks RFC tomorrow:
[https://wiki.php.net/rfc/property-hooks](https://wiki.php.net/rfc/property-hooks)

We have worked long and hard on this RFC, and hope that we have found
some middle-ground that works for the majority. One last concern we
have not officially clarified on the list:

[https://externals.io/message/122445#122667](https://externals.io/message/122445#122667)

I personally do not feel strongly about whether asymmetric types make it into the initial implementation. Larry does, however, and I think it is not fair to exclude them without providing any concrete reasons not to. [snip]

My concern is more about the external impact of what is effectively a change to the type system of the language: [snip] will tools like PhpStan and Psalm require complex changes to analyse code using such properties?

In particular, this paragraph is referencing the ability to widen the
accepted $value parameter type of the set hook, described at the
bottom of [https://wiki.php.net/rfc/property-hooks#set](https://wiki.php.net/rfc/property-hooks#set). I have talked
to Ondřej Mirtes, the maintainer of PHPStan, and he confirmed that
this should not be complex to implement in PHPStan. In fact, PHPStan
already offers the @property-read and @property-write class
annotations, which can be used to describe "virtual" properties
handled within __get/__set, already providing asymmetric types of
sorts. Hence, this concern should be a non-issue.

Thank you to everybody who has contributed to the discussion!

Ilija

On Mon, Apr 8, 2024 at 3:42 PM Ilija Tovilo <tovilo.ilija@gmail.com> wrote:

Hi everyone

Heads-up: Larry and I would like to start the vote of the property
hooks RFC tomorrow:
PHP: rfc:property-hooks

We have worked long and hard on this RFC, and hope that we have found
some middle-ground that works for the majority. One last concern we
have not officially clarified on the list:

[RFC[ Property accessor hooks, take 2 - Externals

>> I personally do not feel strongly about whether asymmetric types make it into the initial implementation. Larry does, however, and I think it is not fair to exclude them without providing any concrete reasons not to. [snip]
>
> My concern is more about the external impact of what is effectively a change to the type system of the language: [snip] will tools like PhpStan and Psalm require complex changes to analyse code using such properties?

In particular, this paragraph is referencing the ability to widen the
accepted $value parameter type of the set hook, described at the
bottom of PHP: rfc:property-hooks. I have talked
to Ondřej Mirtes, the maintainer of PHPStan, and he confirmed that
this should not be complex to implement in PHPStan. In fact, PHPStan
already offers the @property-read and @property-write class
annotations, which can be used to describe "virtual" properties
handled within __get/__set, already providing asymmetric types of
sorts. Hence, this concern should be a non-issue.

Thank you to everybody who has contributed to the discussion!

Ilija

I was playing around with 3v4l.org. Is the implementation up-to-date there?

I don't have any hard objections at the moment, but after playing with
it for a while, I do kind of wonder if it's a lot of complexity for
what is effectively a niche feature because:

1. It does not support asymmetric visibility for get/set. Having a
public getter and private setter seems really natural.
2. You can't access accessors for "siblings".
3. You can't do by-reference set (important for arrays).
4. You can't satisfy a parent's readonly property with a getter in a child.

Now, points 2 through 4 are fairly minor and niche by themselves, but
if we take all these restrictions as a whole... I'm a bit worried.

I also don't like the syntax. I can ignore this for the vote and still
vote yes because I don't like the syntax, but with these other
things... I'm worried.

On Tue, Apr 9, 2024, at 4:07 PM, Levi Morrison wrote:

I was playing around with 3v4l.org. Is the implementation up-to-date there?

I don't have any hard objections at the moment, but after playing with
it for a while, I do kind of wonder if it's a lot of complexity for
what is effectively a niche feature because:

1. It does not support asymmetric visibility for get/set. Having a
public getter and private setter seems really natural.

Aviz is a separate RFC, for reasons we've covered before: They're actually two separate features that do not depend on each other, so we split them up to help reduce RFC size (something RFC authors are often encouraged to do). The Aviz RFC was put to a vote last year but didn't pass. If hooks pass, we do want to revisit aviz and bring it to another vote, as the argument may be stronger now with hooks in place.

2. You can't access accessors for "siblings".

If a use case for this is found, that should be addable in a future RFC with no notable BC break. (Assuming a syntax of self::$otherProp::get() or similar, it would only have the same slight edge case as the parent::$thisProp::get() has, as documented in the RFC. As we've decided that is too edge-casey to care about here, presumably the same would hold true for such an follow-up.)

3. You can't do by-reference set (important for arrays).

This is due to logical limitations in the context. By-ref set means set hooks don't get called. So we can either block by-ref manipulation when a set hook is defined, or we can make set hooks easily-bypassable suggestions at best. We have not found any way around that decision. (And as noted in the RFC, this problem is already present if just using methods. It's not a new issue.)

Please note that *the scope of what is blocked has been reduced considerably from earlier versions of the RFC!* The only invalid combination is &get/set on a backed property, because of the issue above. Writing to an array returned from a hook is allowed in certain circumstances, namely, if it was returned by &get and there is no set.

So we're only preventing the narrowest set of operations we reasonably can, without hamstringing the concept of a set hook in the first place, and we don't believe there is any logical approach that would allow more. If in the future someone can come up with an approach that would work, it would likely be addable in a BC way.

4. You can't satisfy a parent's readonly property with a getter in a child.

In the last week or two we've figured out a situation where we may be able to allow this; iff the child getter works by reading the parent's value, then it would *probably* still be able to satisfy readonly's guarantees. We haven't tried implementing it yet as the RFC is large enough as is, but it's possible that could be added in the future. The main issue is that there's no way to fully guarantee that a get hook on a readonly property is idemopotent, the way a bare readonly property is. (It's not a lack of desire to support it, just the logical issues in ensuring different features' invariants are maintained.)

Note that it's still unclear what set from a child set hook should do, given that readonly properties are also silently private-set, even if the property itself is protected or public. Potentially we could mandate that a child set MUST use parent::$prop::set(), so the actual write is technically in the parent class. I've noted this before as a design flaw in readonly that really needs to be corrected, but that's not a job for this RFC. (We actually had a solution in the aviz RFC, but people pushed back on it so we had to remove it.)

Now, points 2 through 4 are fairly minor and niche by themselves, but
if we take all these restrictions as a whole... I'm a bit worried.

Hopefully the above comments make you less worried. :slight_smile:

--Larry Garfield

On Tue, Apr 9, 2024 at 8:56 PM Larry Garfield <larry@garfieldtech.com> wrote:

On Tue, Apr 9, 2024, at 4:07 PM, Levi Morrison wrote:

> I was playing around with 3v4l.org. Is the implementation up-to-date there?
>
> I don't have any hard objections at the moment, but after playing with
> it for a while, I do kind of wonder if it's a lot of complexity for
> what is effectively a niche feature because:
>
> 1. It does not support asymmetric visibility for get/set. Having a
> public getter and private setter seems really natural.

Aviz is a separate RFC, for reasons we've covered before: They're actually two separate features that do not depend on each other, so we split them up to help reduce RFC size (something RFC authors are often encouraged to do). The Aviz RFC was put to a vote last year but didn't pass. If hooks pass, we do want to revisit aviz and bring it to another vote, as the argument may be stronger now with hooks in place.

> 2. You can't access accessors for "siblings".

If a use case for this is found, that should be addable in a future RFC with no notable BC break. (Assuming a syntax of self::$otherProp::get() or similar, it would only have the same slight edge case as the parent::$thisProp::get() has, as documented in the RFC. As we've decided that is too edge-casey to care about here, presumably the same would hold true for such an follow-up.)

> 3. You can't do by-reference set (important for arrays).

This is due to logical limitations in the context. By-ref set means set hooks don't get called. So we can either block by-ref manipulation when a set hook is defined, or we can make set hooks easily-bypassable suggestions at best. We have not found any way around that decision. (And as noted in the RFC, this problem is already present if just using methods. It's not a new issue.)

Please note that *the scope of what is blocked has been reduced considerably from earlier versions of the RFC!* The only invalid combination is &get/set on a backed property, because of the issue above. Writing to an array returned from a hook is allowed in certain circumstances, namely, if it was returned by &get and there is no set.

So we're only preventing the narrowest set of operations we reasonably can, without hamstringing the concept of a set hook in the first place, and we don't believe there is any logical approach that would allow more. If in the future someone can come up with an approach that would work, it would likely be addable in a BC way.

> 4. You can't satisfy a parent's readonly property with a getter in a child.

In the last week or two we've figured out a situation where we may be able to allow this; iff the child getter works by reading the parent's value, then it would *probably* still be able to satisfy readonly's guarantees. We haven't tried implementing it yet as the RFC is large enough as is, but it's possible that could be added in the future. The main issue is that there's no way to fully guarantee that a get hook on a readonly property is idemopotent, the way a bare readonly property is. (It's not a lack of desire to support it, just the logical issues in ensuring different features' invariants are maintained.)

Note that it's still unclear what set from a child set hook should do, given that readonly properties are also silently private-set, even if the property itself is protected or public. Potentially we could mandate that a child set MUST use parent::$prop::set(), so the actual write is technically in the parent class. I've noted this before as a design flaw in readonly that really needs to be corrected, but that's not a job for this RFC. (We actually had a solution in the aviz RFC, but people pushed back on it so we had to remove it.)

> Now, points 2 through 4 are fairly minor and niche by themselves, but
> if we take all these restrictions as a whole... I'm a bit worried.

Hopefully the above comments make you less worried. :slight_smile:

--Larry Garfield

Off-topic random thought:

The Aviz RFC was put to a vote last year but didn't pass.

It would be really nice if votes weren't just a yes/no vote, but
yes/needs-more-work/no vote, where needs-more-work and no are
effectively the same in terms of passing the RFC, but needs-more-work
just means there is more to do (either addressing ugly syntax or the
idea is sound, but as it says, it needs more work), and can thus be
simply revoted on after concerns are addressed -- instead of creating
a whole new RFC that needs to pass the "not too similar to other RFCs
rule."

I got the impression from the Aviz discussions that most people were
against Aviz due to the syntax, not the feature itself. It would be
absolutely tragic if this failed to pass simply because people
expected Aviz here.

Robert Landers
Software Engineer
Utrecht NL

Hi

On 4/9/24 21:32, Robert Landers wrote:

The Aviz RFC was put to a vote last year but didn't pass.

It would be really nice if votes weren't just a yes/no vote, but
yes/needs-more-work/no vote, where needs-more-work and no are
effectively the same in terms of passing the RFC, but needs-more-work
just means there is more to do (either addressing ugly syntax or the
idea is sound, but as it says, it needs more work), and can thus be
simply revoted on after concerns are addressed -- instead of creating
a whole new RFC that needs to pass the "not too similar to other RFCs
rule."

Re-proposing an RFC is allowed after 6 months even without substantial changes:

Though I'd argue that property hooks passing would be a substantial change of the broader context.

--------------

Relatedly, I would find an official "Abstain" option useful. Any "Abstain" votes would be completely disregarded for the results, but it would allow voters to indicate that they've read the RFC, thought about it and don't sufficiently care either way to influence the results. As an RFC author I think it would be useful information to me: Did some folks not vote because they missed the RFC or because they are happy with either result?

Best regards
Tim DĂĽsterhus

Hi Robert

On Tue, Apr 9, 2024 at 9:34 PM Robert Landers <landers.robert@gmail.com> wrote:

On Tue, Apr 9, 2024 at 8:56 PM Larry Garfield <larry@garfieldtech.com> wrote:
>
> The Aviz RFC was put to a vote last year but didn't pass.

It would be really nice if votes weren't just a yes/no vote, but
yes/needs-more-work/no vote, where needs-more-work and no are
effectively the same in terms of passing the RFC, but needs-more-work
just means there is more to do (either addressing ugly syntax or the
idea is sound, but as it says, it needs more work), and can thus be
simply revoted on after concerns are addressed -- instead of creating
a whole new RFC that needs to pass the "not too similar to other RFCs
rule."

The asymmetric visibility RFC did include a poll for no votes.
https://wiki.php.net/rfc/asymmetric-visibility#proposed_voting_choices

I got the impression from the Aviz discussions that most people were
against Aviz due to the syntax, not the feature itself. It would be
absolutely tragic if this failed to pass simply because people
expected Aviz here.

According to the poll, syntax was one, but not the primary reason for
its rejection. The primary reason was that some people don't believe
the feature is necessary at all.

IIRC, people were arguing that readonly covers 80% of use-cases,
because it protects against writes to the property both publicly and
privately. I don't agree with this viewpoint, because I think readonly
is bad for ergonomics. In fact, we already had an RFC that attempted
to fix clone for readonly
(https://wiki.php.net/rfc/readonly_amendments) but this fix was not
complete (because it's still not possible to pass values from clone to
__clone). "Clone with" is another thing needed to fix this, and at
this point it just feels like applying more band-aids.

For DTOs, I believe value types (i.e. data classes,
[RFC][Concept] Data classes (a.k.a. structs) - Externals) solve the problem of "spooky
actions at a distance" in a cleaner and more ergonomic way.

For services and other intentional reference types, readonly often
isn't the right choice either, just to make the property not publicly
writable. Asymmetric visibility would be a much more fitting choice.

Anyway, we didn't include asymmetric visibility in this RFC because:

* We wanted to avoid getting rejected by people who fundamentally
dislike asymmetric visibility.
* We didn't feel it was fair to "sneak" the feature back in through
some other RFC, when it was explicitly rejected.

Instead, we are planning to re-propose asymmetric visibility once
property hooks are merged, as it may become more apparent why it is
useful.

Ilija

Instead, we are planning to re-propose asymmetric visibility once
property hooks are merged, as it may become more apparent why it is
useful.

I was talking today to a co-worker about this internals e-mail discussion and he thought asymmetric visibility was inherent to property-hooks.
He even was surprised by the possibility of having it without Aviz. He said that because he has a C# background. I really think that property hooks will highlight the benefits of Aviz.
Hey, voter right beholders, please give this awesome feature a strong yes vote!

–

Regards,
Erick

Later than intended, but here goes…

If there is one RFC which has been giving me nightmares since I first heard of it, it’s this one.

I realize it is late in the discussion period to speak up, but for months I’ve been trying to find the words to express my concerns in a polite and constructive way and have failed.

I am going to try now anyway (before it is too late), so please bear with me. Also, as I’m not a C-developer, please forgive me if I get the internals wrong. I’m writing this from a PHP-dev/user perspective, with my perspective being heavily influenced by my role as maintainer of PHP_CodeSniffer.

···

On 8-4-2024 23:39, Ilija Tovilo wrote:

Hi everyone

Heads-up: Larry and I would like to start the vote of the property
hooks RFC tomorrow:
[https://wiki.php.net/rfc/property-hooks](https://wiki.php.net/rfc/property-hooks)

We have worked long and hard on this RFC, and hope that we have found
some middle-ground that works for the majority. One last concern we
have not officially clarified on the list:

[https://externals.io/message/122445#122667](https://externals.io/message/122445#122667)

I personally do not feel strongly about whether asymmetric types make it into the initial implementation. Larry does, however, and I think it is not fair to exclude them without providing any concrete reasons not to. [snip]

My concern is more about the external impact of what is effectively a change to the type system of the language: [snip] will tools like PhpStan and Psalm require complex changes to analyse code using such properties?

In particular, this paragraph is referencing the ability to widen the
accepted $value parameter type of the set hook, described at the
bottom of [https://wiki.php.net/rfc/property-hooks#set](https://wiki.php.net/rfc/property-hooks#set). I have talked
to Ondřej Mirtes, the maintainer of PHPStan, and he confirmed that
this should not be complex to implement in PHPStan. In fact, PHPStan
already offers the @property-read and @property-write class
annotations, which can be used to describe "virtual" properties
handled within __get/__set, already providing asymmetric types of
sorts. Hence, this concern should be a non-issue.

Thank you to everybody who has contributed to the discussion!

Ilija

On Wed, Apr 10, 2024 at 5:47 AM Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> wrote:

On 9-4-2024 16:03, Juliette Reinders Folmer wrote:

On 8-4-2024 23:39, Ilija Tovilo wrote:

Hi everyone

Heads-up: Larry and I would like to start the vote of the property
hooks RFC tomorrow:
[https://wiki.php.net/rfc/property-hooks](https://wiki.php.net/rfc/property-hooks)

We have worked long and hard on this RFC, and hope that we have found
some middle-ground that works for the majority. One last concern we
have not officially clarified on the list:

[https://externals.io/message/122445#122667](https://externals.io/message/122445#122667)

I personally do not feel strongly about whether asymmetric types make it into the initial implementation. Larry does, however, and I think it is not fair to exclude them without providing any concrete reasons not to. [snip]

My concern is more about the external impact of what is effectively a change to the type system of the language: [snip] will tools like PhpStan and Psalm require complex changes to analyse code using such properties?

In particular, this paragraph is referencing the ability to widen the
accepted $value parameter type of the set hook, described at the
bottom of [https://wiki.php.net/rfc/property-hooks#set](https://wiki.php.net/rfc/property-hooks#set). I have talked
to Ondřej Mirtes, the maintainer of PHPStan, and he confirmed that
this should not be complex to implement in PHPStan. In fact, PHPStan
already offers the @property-read and @property-write class
annotations, which can be used to describe "virtual" properties
handled within __get/__set, already providing asymmetric types of
sorts. Hence, this concern should be a non-issue.

Thank you to everybody who has contributed to the discussion!

Ilija

Ilija,

Heads-up: I’m still writing up an opinion and intend to send it to the list before end of day (CET). I know I’m late to the party, but I’ve been having trouble finding the words to express myself properly regarding this RFC (and have been struggling to find the right words for months).

Smile,
Juliette

Later than intended, but here goes…

If there is one RFC which has been giving me nightmares since I first heard of it, it’s this one.

I realize it is late in the discussion period to speak up, but for months I’ve been trying to find the words to express my concerns in a polite and constructive way and have failed.

I am going to try now anyway (before it is too late), so please bear with me. Also, as I’m not a C-developer, please forgive me if I get the internals wrong. I’m writing this from a PHP-dev/user perspective, with my perspective being heavily influenced by my role as maintainer of PHP_CodeSniffer.


TL;DR: this RFC tries to do too much in one go and introduces a huge amount of cognitive complexity with all the exceptions and the differences in behaviour between virtual and backed properties. This cognitive complexity is so high that I expect that the feature will catch most developers out a lot of the time.

I can definitely see the use case and desirability of the property hooks functionality proposed in the RFC and compared to the initial RFC I read last year, the current RFC is, IMO, much improved.
Huge kudos to Ilija and Larry for all the work they have put in to this!

I applaud the intention of this RFC to make it easier to avoid the magic __get()/__set() et al methods. What I have a problem with is the implementation details.

Over the last few years, we’ve seen a movement to get rid of more and more of the surprising behaviour of PHP, with subtle exceptions being deprecated and slated for removal and (most) new syntaxes trying to use the principle of least surprise by design.

This RFC, in my view, is in stark contrast to this as it introduces a plethora of exceptions and subtle different behaviour in a way that will catch developers out for years to come.

At this moment (excluding property hooks), from a user perspective, there are three different function syntaxes in PHP: named functions (and methods), anonymous functions and arrow functions.

The semantics of these are very similar with subtle differences:

  • Can be static or non-static.
  • Take parameters, which can be typed, references, variadic, optional etc.
  • Can have a return type.
  • Can return by reference.
  • Have a function “body”.

The differences between the current syntaxes - from a user perspective - are as follows:
= Named functions:

  • When declared in a class, can have visibility attached, can be abstract, can be final.
  • When declared in an interface or declared as abstract, will not have a function “body”.

= Anonymous functions:

  • Can import plain variables from outside its scope with a use() clause.
  • Are declared as an expression (can be assigned to a variable etc).

= Arrow functions:

  • Have access to variables in the same scope.
  • Are declared as an expression.
  • Body of the function starts with a => instead of being enclosed in curlies and can end on a range of characters.
  • Can only take one statement in the body.
  • Automagically returns.

The property hooks RFC introduces a fourth flavour of function syntax. And not just one syntax, but five and the differences in the semantics of the function syntaxes are significant.

The differences in semantics I see for “full” property hook functions compared to other function syntaxes are as follows:

  • get cannot take parameters (and doesn’t have the parentheses typically expected for a function declaration).
  • get cannot take return type (inherits this from the property, which is logically sound, but does create a syntactic difference).
  • set can take one (typed or untyped) parameter.
  • set cannot take return type (silently set to void, which is logically sound, but does create a syntactic difference).
  • set magically creates a $value variable for the “new” value, though that variable may have an arbitrary different name depending on whether or not the parameter was explicitly specified.
  • Has a function body.

Then there are multiple short hand syntaxes, which each have yet more syntactic differences:

  • The arrow function variant for get with all the above differences + the differences inherent to arrow functions, combined, with the exception of the access to variables in the same scope and a more clearly defined end of the expression.
  • The implicit “set” parameter, which does not have a explicit parameter, does not have parentheses and has the magically created $value variable.
  • The arrow function variant for set with all the above differences + the differences inherent to arrow functions with the above mentioned exceptions + the implicit assignment, which breaks the expected behaviour of arrow functions by assigning the result of the expression instead of returning it (totally understandable, but still a difference).
  • The abstract/interface variants, which don’t have a function body.

Next there are the differences in semantics which are now being introduced for properties:

  • Aside from the hooks themselves…
  • Properties may or may not have a default value anymore, depending on whether the hooks cause it to be a backed or a virtual property.
  • Properties with hooks can be readonly, but cannot be declared as readonly.

And then there are the new features for properties (not just hooked ones):

  • Properties can now be declared as final.
  • Properties can now be declared as abstract, but only with explicit hook requirements, otherwise the abstract keyword is not allowed.
  • Properties can now be declared on interfaces, but only with explicit hook requirements, otherwise they are not allowed.
  • Abstract/Interface properties don’t comply with the same covariance/contravariance rules as other properties

This complexity describes what I’ve been unconsciously worried about though I really like having the proposed feature in PHP. I’m not sure if there’s a solution to reduce the complexity to a point where glancing at something will tell you how it works when you don’t know exactly how all variants work. There are already a lot of gotches with something as simple as adding a string|null to a property without setting its default value to null. It’s one of those things you won’t find out until you run that code because technically speaking it’s not incorrect. I worry that a bunch of those gotchas will bite me in the behind because I don’t fully understand how it works and mistakes are easily made. It also adds a lot of complexity when reviewing someone else’s code where bugs might slip through.

And last but not least, there is the ability to declare property hooks in constructor property promotion … where we can now basically have a function declaration within the signature of a method declaration… with all five possible syntax types.

This is what I’ve been afraid of. Constructor method signatures are already exploding and I hope that one day we’ll revise this notation and head towards __construct($this->theProperty) instead. One could argue that property promotion is fully optional, except that in its most basic design it does save a lot of lines of code and is a net-gain in terms of maintainability and readability in its minimum form for properly written code. The benefits fall apart the moment you don’t have static code analysis available, or if the class is big/old and it just takes time for the tool to be done. If I forget to add the visibility keyword in the constructor I have to rely on my IDE telling me that the argument is unused, which results in extra overhead of trying to find out why it’s telling me it’s unused, just to realize I forgot to add public or private.

Some of my worries would be removed if hooks are not allowed in property promotion scenarios, but this doesn’t solve the complexity mentioned for the hooks themselves.

So seeing how big this feedback was, I gave the RFC a proper read before I read this feedback message and frankly, as a userland developer, I checked out at the “Here is a exhaustive list of possible hook combinations and their supported operations.” and just stopped reading because at that point my brain seized up and I understand nothing. What’s more damming - I do not want to understand it and I’m looking at it as “I’m gonna have a .git hook that prohibits hooks period - it’s so complicated and convoluted that I do not trust with it myself and not even talking about less experienced developers in my teams”.

The amount of complexity in these two hooks is on the level of the whole PHP OOP model with the number of footguns and WTF cases to rival magic quotes, register_globals=on and every other infamous gotcha we had in the engine that got deleted out of existence.

And then there’s the whole “chained methods” part that’s missing at all. If I want chained methods, that means the setter hooks are useless for me, meaning we are back to just using get*() and set*() methods anyway, making the hooks kind’a irrelevant.

···

Arvīds Godjuks+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius

On Wed, Apr 10, 2024 at 5:49 AM Juliette Reinders Folmer
<php-internals_nospam@adviesenzo.nl> wrote:

On 9-4-2024 16:03, Juliette Reinders Folmer wrote:

On 8-4-2024 23:39, Ilija Tovilo wrote:

Hi everyone

Heads-up: Larry and I would like to start the vote of the property
hooks RFC tomorrow:
PHP: rfc:property-hooks

We have worked long and hard on this RFC, and hope that we have found
some middle-ground that works for the majority. One last concern we
have not officially clarified on the list:

[RFC[ Property accessor hooks, take 2 - Externals

I personally do not feel strongly about whether asymmetric types make it into the initial implementation. Larry does, however, and I think it is not fair to exclude them without providing any concrete reasons not to. [snip]

My concern is more about the external impact of what is effectively a change to the type system of the language: [snip] will tools like PhpStan and Psalm require complex changes to analyse code using such properties?

In particular, this paragraph is referencing the ability to widen the
accepted $value parameter type of the set hook, described at the
bottom of PHP: rfc:property-hooks. I have talked
to Ondřej Mirtes, the maintainer of PHPStan, and he confirmed that
this should not be complex to implement in PHPStan. In fact, PHPStan
already offers the @property-read and @property-write class
annotations, which can be used to describe "virtual" properties
handled within __get/__set, already providing asymmetric types of
sorts. Hence, this concern should be a non-issue.

Thank you to everybody who has contributed to the discussion!

Ilija

Ilija,

Heads-up: I'm still writing up an opinion and intend to send it to the list before end of day (CET). I know I'm late to the party, but I've been having trouble finding the words to express myself properly regarding this RFC (and have been struggling to find the right words for months).

Smile,
Juliette

Later than intended, but here goes....

If there is one RFC which has been giving me nightmares since I first heard of it, it's this one.

I realize it is late in the discussion period to speak up, but for months I've been trying to find the words to express my concerns in a polite and constructive way and have failed.

I am going to try now anyway (before it is too late), so please bear with me. Also, as I'm not a C-developer, please forgive me if I get the internals wrong. I'm writing this from a PHP-dev/user perspective, with my perspective being heavily influenced by my role as maintainer of PHP_CodeSniffer.

---
TL;DR: this RFC tries to do too much in one go and introduces a huge amount of cognitive complexity with all the exceptions and the differences in behaviour between virtual and backed properties. This cognitive complexity is so high that I expect that the feature will catch most developers out a lot of the time.
---

I can definitely see the use case and desirability of the property hooks functionality proposed in the RFC and compared to the initial RFC I read last year, the current RFC is, IMO, much improved.
Huge kudos to Ilija and Larry for all the work they have put in to this!

I applaud the intention of this RFC to make it easier to avoid the magic __get()/__set() et al methods. What I have a problem with is the implementation details.

Over the last few years, we've seen a movement to get rid of more and more of the _surprising_ behaviour of PHP, with subtle exceptions being deprecated and slated for removal and (most) new syntaxes trying to use the principle of least surprise by design.

This RFC, in my view, is in stark contrast to this as it introduces a plethora of exceptions and subtle different behaviour in a way that will catch developers out for years to come.

At this moment (excluding property hooks), from a user perspective, there are three different function syntaxes in PHP: named functions (and methods), anonymous functions and arrow functions.

The semantics of these are very similar with subtle differences:
* Can be static or non-static.
* Take parameters, which can be typed, references, variadic, optional etc.
* Can have a return type.
* Can return by reference.
* Have a function "body".

The differences between the current syntaxes - from a user perspective - are as follows:
= Named functions:
* When declared in a class, can have visibility attached, can be abstract, can be final.
* When declared in an interface or declared as abstract, will not have a function "body".

= Anonymous functions:
* Can import plain variables from outside its scope with a `use()` clause.
* Are declared as an expression (can be assigned to a variable etc).

= Arrow functions:
* Have access to variables in the same scope.
* Are declared as an expression.
* Body of the function starts with a => instead of being enclosed in curlies and can end on a range of characters.
* Can only take one statement in the body.
* Automagically returns.

The property hooks RFC introduces a fourth flavour of function syntax. And not just one syntax, but five and the differences in the semantics of the function syntaxes are significant.

The differences in semantics I see for "full" property hook functions compared to other function syntaxes are as follows:
* `get` cannot take parameters (and doesn't have the parentheses typically expected for a function declaration).
* `get` cannot take return type (inherits this from the property, which is logically sound, but does create a syntactic difference).
* `set` can take one (typed or untyped) parameter.
* `set` cannot take return type (silently set to void, which is logically sound, but does create a syntactic difference).
* `set` magically creates a $value variable for the "new" value, though that variable _may_ have an arbitrary different name depending on whether or not the parameter was explicitly specified.
* Has a function body.

Then there are multiple short hand syntaxes, which each have yet more syntactic differences:
* The arrow function variant for `get` with all the above differences + the differences inherent to arrow functions, combined, with the exception of the access to variables in the same scope and a more clearly defined end of the expression.
* The implicit "set" parameter, which does not have a explicit parameter, does not have parentheses and has the magically created $value variable.
* The arrow function variant for `set` with all the above differences + the differences inherent to arrow functions with the above mentioned exceptions + the implicit assignment, which breaks the expected behaviour of arrow functions by assigning the result of the expression instead of returning it (totally understandable, but still a difference).
* The abstract/interface variants, which don't have a function body.

Next there are the differences in semantics which are now being introduced for properties:
* Aside from the hooks themselves....
* Properties _may_ or _may not_ have a default value anymore, depending on whether the hooks cause it to be a backed or a virtual property.
* Properties with hooks can _be_ readonly, but cannot be declared as readonly.

And then there are the new features for properties (not just hooked ones):
* Properties can now be declared as final.
* Properties can now be declared as abstract, but only with explicit hook requirements, otherwise the abstract keyword is not allowed.
* Properties can now be declared on interfaces, but only with explicit hook requirements, otherwise they are not allowed.
* Abstract/Interface properties don't comply with the same covariance/contravariance rules as other properties

And last but not least, there is the ability to declare property hooks in constructor property promotion ... where we can now basically have a function declaration within the signature of a method declaration.... with all five possible syntax types.

Additionally, when reading the RFC (in its current state), I see the following exceptions being put in place, which impact the semantics for properties:
* Only available for object properties, static properties are excluded.
* `var` for property declarations is not supported according to the RFC. While I 100% agree using the var keyword is old-school, using `var` effectively makes a property a public property (which would satisfy the visibility requirement for an interface/abstract class) and the `var` keyword is *not* deprecated, even though the RFC states it is: Online PHP editor | output for 3o50a
   I'd fully support formally deprecating the `var` keyword for properties and eventually removing it, but not supporting it for this RFC, even though it is still a supported language feature seems opinionated and arbitrary.
   Note: when testing the RFC code, declaring a property using the var keyword on an interface does not currently throw an error (while if it is not supported, I would expect one): https://3v4l.org/KaLqe/rfc#vrfc.property-hooks , same goes for using the var keyword with property hooks in a class: Online PHP editor | rfc for mQ6pG
* Disallows references to properties, but only when there is a set hook.
* Disallows indirect modification of a property, but only when there is a set hook.
* Write-only (virtual) properties, which cannot be read and you need to study the hook declaration in detail to figure out if a property is or is not write-only.
   Oh, and hang on, they *can* be read from a method in the same class as long as that method is called from within the set hook, so now the method will need to either do a backtrace or use Reflection to figure out whether it has access to the property value (now why does that remind me of the magic __...() methods ?).
* Readonly properties which are not denoted as readonly, but still are due to their virtual nature (get without access to $this->prop), which can only be figured out by, again, studying the contents of the hook function.
* Whether a type can be specified on the parameter on `set` depends on whether the property is typed. You cannot declare `set(mixed $value)` for an untyped property, even though it would effectively be compatible. This is inconsistent with the behaviour for, for instance method overloads, where this is acceptable: https://3v4l.org/hbCor/rfc#vrfc.property-hooks , though it is consistent with the behaviour of property overloads, where this is not acceptable: https://3v4l.org/seDWM (anyone up for an RFC to fix this inconsistency ?)
* Changes the meaning of `$this->property` read/write access for all methods called from within a hook while executing the hook.
* Creates two different flavour of properties: "backed" properties and "virtual" properties with significantly different behaviours, raising cognitive complexity.
    This includes, but is not limited to the fact that set hooks can be bypassed on virtual properties via a get reference.
* The range of different behaviours for various forms of serialization.

Furthermore:

[1] The RFC also states in "Interaction with isset() and unset()" that the behaviour of property hooks is consistent with how `isset()` interacts with `__get()` today. This is not true, as in: the behaviour as described for isset with magic methods is different than what the RFC states it is: Online PHP editor | rfc for 2Arkh

Additionally, it proposes for isset() to throw an Error for virtual properties without a get hook. This is surprising behaviour as isset() by its nature is expected to *not* throw errors, but return false for whatever is not set or inaccessible from the current context: https://3v4l.org/3OlgM

[2] PHP supports "multi-property declarations", but I see no mention of these in the RFC, which makes it unclear if and if so, how property hooks would work with multi-property declarations.
class Foo {
    public string $bar, $baz, $bab;
}

[3] If a `set` hook gets a named parameter, is the magic `$value` variable still available or not ? I'd expect not, but it is unclear from the RFC.

[4] Why should ReflectionProperty::getRawValue() throw an error for static properties ? Instead of returning the value, identically to ReflectionProperty::getValue() ? This is surprising to me and I see no reason for it.

All in all, I really do see the use case for this feature and I most definitely appreciate all the work Ilija and Larry have put into the RFC and the implementation.

However, in its current state, I'm concerned that the feature introduces so many exceptional situations and WTF moments that it is a worse solution than the magic methods which are already available. (and no, I'm definitely not a fan of those magic methods either).

I think property hooks as currently proposed with all its catches will be hard to teach, will raise the barrier of entry to the language, can catch people out in dozens of different ways and introduces way too many peculiarities, while the tendency of the language has been to try to eliminate those kind of surprising behaviours.

Irrelevant side-note: I kind of feel like I could create a completely new "Why Equal doesn't Equal" Quiz just and only based on property hooks and people will still not get it afterwards.

---

As for the impact on static analysis tools....

I can't speak for other tools, but I can tell you that for PHP_CodeSniffer the impact of this RFC will be horrendous. Aside from the impact on the syntax recognition layer (Tokenizer), my rough estimate is that about 70% of all sniffs ever written will need to be adjusted for these five syntaxes and all the peculiarities around them - either to ignore hooks, work around hooks, skip over hooks, or to take hooks into account and examine them in all their variations.
I estimate it will take a good 5-6 years before all actively used sniffs will have been adjusted and that in the mean time, maintainers of both PHPCS itself as well as the popular external standards will have to deal with a significant number of extra support requests/bug reports related to this.

The sheer amount of new syntaxes which have been introduced starting with PHP 7.4, has already brought innovation in a large part of the PHP_CodeSniffer field to a near complete halt for the past four years as I'm continuously trying to play catch-up. This RFC will mean that innovation, like new PSR-5/19 Docs standards, QA sniffs for tests, significantly improving the Security standard etc, will all be put on hold for another few years while I have to deal with this change.

While I fully recognize that the impact on userland tooling is not a reason to reject a new PHP feature, nor should it be, I do believe that the impact of this RFC could be far less and easier to mitigate if different design choices were made or if the new syntaxes were introduced in a far slower, more staged approach across multiple PHP versions.

Smile,
Juliette

P.S.: nitpick: in the first two code examples under "Detailed Implementation" - "Set" the `$u` variable is missing the dollar sign for the `new User` line.

As someone who has spent a number of years writing C#, the RFC looks
quite sane. I do agree that it does quite a lot in one go though. If
you look at how fields were introduced into C#, the "short syntax"
version didn't come along until years after the long version and
people had a good grasp of what they wanted: a shorter way to write
fields because writing it out all the time was annoying af. Getting it
from the start will be really nice!

Personally, I'm not a big fan of calling arbitrary methods from inside
setters/getters. I'm pretty sure this is allowed in C#, however, it is
an anti-pattern and avoided except for very narrow edge cases. I'm
sure we'll see some of that here, where people will get bitten, and a
consensus of anti-patterns will emerge. However, having that power
available is immensely useful /when you need it/! Removing it because
"it complicates things" isn't really a good reason, IMHO.

To me, as a non-voting outsider, it smells like the RFC process breaks
down past a certain point. I've watched operator overloading fail ...
and I am implementing an RFC that could have been done 1000% in
user-space if operator overloading had passed. But it didn't, so now
we need to literally change the language to support it. Then watching
the feedback here, at the 11th hour, is super saddening. Will we never
be able to make great changes to the language due to a tragedy of the
commons?

Robert Landers
Software Engineer
Utrecht NL

Hi,

Il 10/04/2024 05:40, Juliette Reinders Folmer ha scritto:

---
TL;DR: this RFC tries to do too much in one go and introduces a huge amount of cognitive complexity with all the exceptions and the differences in behaviour between virtual and backed properties. This cognitive complexity is so high that I expect that the feature will catch most developers out a lot of the time.
---

Thanks Juliette for the excellent write-up (I quoted the TL;DR just for brevity).

At first what the RFC promises looks incredibly shiny and got me very excited. The work done by Larry and Ilija is certainly brilliant, but the complexity is so big that I feel we should aim for a trimmed down version to make it easier to grasp and to work with, e.g. less syntax variants (no short or constructor promotion), and perhaps no virtual properties.

Cheers
--
Matteo Beccati

Development & Consulting - http://www.beccati.com/

As someone who has spent a number of years writing C#, the RFC look> quite sane.
Same here.

Removing it because “it complicates things” isn’t really a good reason, IMHO.

I think the same. FFI, for instance, is powerful but one can make a mess with it.

Then watching the feedback here, at the 11th hour, is super saddening.

Again, same here . If this feedback was given during the “heat of the discussion”, maybe it could lead the discussion to a place where less variants would
be introduced on this first implementation and a shorter syntax could be introduced later on, like the arrow functions were introduced after anonymous functions.

Maybe if such a feedback was given before and it was decided to go for a trimmed version of the feature, maybe Ilija/Larry could have had less work to implement and test all the variantes they’ve done. I think if a person has such numerous concerns, it should be exposed ASAP even if your thoughts are not totally clear. If you had a chat directly with Ilija/Larry, they would be aware of such concerns and would expend efforts to address these concerns.

Regards,
Erick

On 10/04/2024 15:56, Alex Wells wrote:

To an outsider, it looks wild when feedback starts coming in right before the vote starts. What's even more startling is that there are people with voting rights who have never participated in the discussion at all, yet have a right to wordlessly affect the vote's outcome. I sincerely hope Ilija and Larry's work don't go to waste here.

There are always people afraid of change. I just hope they don't stand in the way of progress.

Is the feature complex? Yes. Is it overly complex? No. As a developer who has hand-rolled custom getter/setter behavior for years this will save a mountain of time (and would’ve removed nearly half of the code I once had to write for a very complicated WordPress feature). I love the approach and wholeheartedly support the direction here. The similarity to C# and Kotlin structures should lower the cognitive burden here as well.

···

On 4/10/24 07:56, Alex Wells wrote:

To an outsider, it looks wild when feedback starts coming in right before the vote starts. What’s even more startling is that there are people with voting rights who have never participated in the discussion at all, yet have a right to wordlessly affect the vote’s outcome. I sincerely hope Ilija and Larry’s work don’t go to waste here.

–
Clearly those of us who agreed with the approach and wanted the RFC and planned to vote to approve things should’ve been more vocal about that. Don’t take silence to be apathy - email is often the worst medium for a deep discussion and “ditto” doesn’t add to the discourse.

I for one will try to be more vocal since apparently what I thought was noise is apparently necessary signal here.

On Wed, Apr 10, 2024 at 4:01 PM Erick de Azevedo Lima <ericklima.comp@gmail.com> wrote:

Maybe if such a feedback was given before and it was decided to go for a trimmed version of the feature, maybe Ilija/Larry could have had less work to implement and test all the variantes they’ve done. I think if a person has such numerous concerns, it should be exposed ASAP even if your thoughts are not totally clear. If you had a chat directly with Ilija/Larry, they would be aware of such concerns and would expend efforts to address these concerns.

To an outsider, it looks wild when feedback starts coming in right before the vote starts. What’s even more startling is that there are people with voting rights who have never participated in the discussion at all, yet have a right to wordlessly affect the vote’s outcome. I sincerely hope Ilija and Larry’s work don’t go to waste here.

Hi Ilija, Larry,

Heads-up: Larry and I would like to start the vote of the property
hooks RFC tomorrow:
https://wiki.php.net/rfc/property-hooks

I would make 3 changes to the RFC if I were in charge :

  • I’d make $this->propName inside a hook access the parent property via its hooks if any. I’d do this because that’d provide encapsulation-by-default for inheritance. Aka a parent class could reasonably expect its child classes to get through its hooks. (I’d be fine with only allowing the “=” operator on this.)

  • I’d keep the special $field value to access the backing value. It’s a clear way to signify that accessing the raw value is possible only in hooks, never in other methods.

  • And I’d consider forbidding any method calls in hooks except static methods. This would allow factorising functional logic without opening for side-effects on the object outside of hooks themselves.
    I’m sharing this in case it rings a bell somewhere, but I can work with the peculiarities of the current RFC and I must admit I didn’t spend as much time as you on the topic so my proposals might fall short, while yours to actually work. Thanks for that :slight_smile:

I’m in favor of the RFC.

Cheers,
Nicolas

Hi,

To an outsider, it looks wild when feedback starts coming in right before the vote starts. What's even more startling is that there are people with voting rights who have never participated in the discussion at all, yet have a right to wordlessly affect the vote's outcome. I sincerely hope Ilija and Larry's work don't go to waste here.

I was also one of those who did not speak out because I was in favor of this RFC.

The syntax is new and may look unfamiliar, but people should soon get used to it. Also, I personally don't think the functionality of property hooks is complicated. Since this is a new feature, the amount of information may be a little large during initial learning, but the feature itself is not that complicated.

Regards,

Saki

1 Like

Reminder to not run into a sunk cost fallacy.
I totally understand the sentiment around the work done, but the outcome is something very impactful.
Work may or may not be wasted, but that’s the nature of RFCs.

The current RFC makes a lot of people uneasy, and JRF’s comment resonates strongly with my opinion.

To me, further overloading the arrow (->) operator with this much added context is a mistake, regardless of how much detail was added in handling all the weird edge cases of the language: in fact, going as far as exploring by-ref semantics (instead of just saying “no” to them) is also a big problem.

···

Marco Pivetta

https://mastodon.social/@ocramius

https://ocramius.github.io/