[PHP-DEV] [RFC] Readonly property hooks

On Fri, Jul 18, 2025, at 11:29 AM, Tim Düsterhus wrote:

// New code in 8.5:

$p = new PositivePoint(3, 4);
$p2 = clone($p, ['x' => -10]);

This is not legal code in PHP 8.5. Clone-with respects visibility and
since your asymmetric visibility RFC included the change, you are
probably aware that `readonly` implies `protected(set)`.

5.

but are now necessary to ensure that invariants are enforced.

And therefore with PHP 8.5 hooks are not necessary to enforce
invariants, except in the rare case where a `public(set) readonly`
property is used.

This is a valid point, thanks. I have updated the example to use `public(set)`, and note that it means many withX() methods can be eliminated, as their restrictions can be placed on the property instead.

Note to self: public(clone) ...

6.

So no guarantees are softened by this RFC.

Yes, they are. Unless `__get()` is implemented on a class (which is
explicitly visible as part of the public API), readonly guarantees the
immutability of identity.

Which would also be guaranteed with the "refers to the backing value" proposal, which you also rejected.

Does readonly refer to the physical value (backing value, object identity) or the "value returned"? That wasn't a distinction that mattered in 8.1 when readonly was introduced, though now it does. If yes, then all that needs to be protected is the backing value, so get hooks that respect that should be fine.

Does readonly refer to the value returned? If so, that's already been broken since the beginning because the property can be a mutable object, so trusting the data returned to be "the same" is already not safe.

At a conceptual level, we need to decide what readonly means. There is clearly no consensus on that right now, and that's going to cause problems any time we touch readonly, not just this RFC.

While that is an interesting idea that has been floated a few times, it has enough complexities and edge cases of its own to address that we feel it is out of scope.

While it certainly is your right as the RFC authors to consider certain
things out of scope for an RFC, I strongly oppose the notion of shipping
something that is strictly inferior and comes with obvious semantic
issues due to perceived complexity of another solution and then
following up with the proper solution that has already been identified.
As I've outlined in my previous emails, I found defining semantics for
an 'init' hook straight-forward when looking at how PHP works as of today.

I suppose this is a valid argument, given that readonly was implemented because Nikita felt the proper solution of full aviz would be too complex so went with the simpler solution, which, turns out, is strictly inferior and comes with obvious semantic issues.

After reading through the discussion, it seems the only argument against
the 'init' hook is perceived complexity. It is not at all clear to me
why this means that we must now rush something with clear issues into
PHP 8.5.

At this point, I fully expect the 'get' part of the RFC to not pass. Someone else is welcome to then work on an init hook. The seemingly less controversial 'set' hook still has considerable value on its own, and hopefully that passes.

--Larry Garfield

On Fri, 2025-07-18 at 11:49 -04:00, Rob Landers <rob@bottled.codes> wrote:

On Fri, Jul 18, 2025, at 17:25, Tim Düsterhus wrote:

A readonly class is not just a convenience shortcut to mark each
individual property as readonly. It has important semantics of its own,
because it forces child classes to also be readonly. And even for final
classes it communicates to the user that “I won’t be adding non-readonly
properties to the class”.

Wasn’t that the entire point of readonly classes? Because it was painful to write readonly for every property. Then if a property is readonly, the inherited property is also readonly, so, by extension: a class extending a readonly class is also readonly.

There’s no “communication” here; just logic.

All code is communication, not just to the computer, but to other humans reading it. This is why the semantics of the terms are important. I agree with Tim’s interpretation of those semantics, and hence vastly prefer an init hook to get in these circumstances.

Importantly, this RFC can clarify the semantics of these terms for the language as a whole. That specific meaning can be documented. If a proposal does not come from a principled understanding of what readonly means - what it indicates to users of the class - I think that is a step backwards.

mjec

Nick, Larry,

On Fri, Jul 18, 2025 at 2:01 PM Nicolas Grekas
<nicolas.grekas+php@gmail.com> wrote:

Le ven. 18 juil. 2025 à 18:32, Tim Düsterhus <tim@bastelstu.be> a écrit :

Hi

On 7/17/25 18:26, Larry Garfield wrote:
> Given the lack of consensus both here and in off-list discussions on how to handle get hooks, we have done the following:
>
> * Split the RFC into two sections, one for get, one for set.
> * Expanded and refined the examples for both. The implementation is still the original, however.
> * Split the vote into two: one for allowing readonly get hooks, one for readonly set hooks.
>
> We will start the vote sometime this weekend, most likely, unless some major feedback appears before then, and let the chips fall where they may.

After working through (most of) the discussion, I've now taken a look at
the updated RFC. I have the following remarks:

1.

> It is really “write-once”, which is not the same as immutable (as shown above). But there's no reason that “write-once” need be incompatible with hooks.

This is a strawman argumentation, as I've outlined in my previous
emails, calling readonly "write-once" is wrong. It is a reasonable user
expectation to always get the identical value when reading a value that
may only be set once. By calling it "write-once" you are trying to shift
the focus to the write operation, which is totally irrelevant for user
expectations when interacting with readonly properties. Especially for
expectations of users that just *use* a class rather than writing one.

To my ears, write-once is more accurate than readonly because it sticks to the facts of how this behaves. That's very relevant.
Using readonly to suggest immutable is where the arguments for rejecting this RFC are weak.
readonly doesn't mean immutable, no matter how hard some want it to be...

(including a snippet from a separate email from Larry below)

Does readonly refer to the value returned? If so, that's already been broken since the beginning because the property can be a mutable object, so trusting the data returned to be "the same" is already not safe.

It seems to me that the original intent of `readonly` was to mean
immutable, and points to a property always equaling itself in the
rationale section
(PHP: rfc:readonly_properties_v2):

$prop = $this->prop;
$fn(); // Any code may run here.
$prop2 = $this->prop;
assert($prop === $prop2); // Always holds.

It even calls out that this *does not* restrict *interior* mutability,
which I believe you are using to argue that it doesn't actually mean
immutable:

"However, readonly properties do not preclude interior mutability.
Objects (or resources) stored in readonly properties may still be
modified internally."

This is exactly how I think about `readonly`. The identity of property
won't change, but the object itself might. Now if the object is also a
`readonly` class (and recursive for any of those class's properties
that are objects), then you *would* truly have an immutability
guarantee of both the identity of the object and the object's
properties. This is ignoring __get, which I have pointed out elsewhere
is worth ignoring, since we can conceivably remove that from readonly
classes if we pass `init` hooks.

6.

> So no guarantees are softened by this RFC.

Yes, they are. Unless `__get()` is implemented on a class (which is
explicitly visible as part of the public API), readonly guarantees the
immutability of identity.

Which is not really relevant when talking about immutability.
What everybody is looking for when using that word is immutable objects.

This is not what I am looking for, so I disagree. I would like
immutable objects as well, but unless the object itself is a readonly
class as noted above, I would not expect it to mean immutable objects.

7.

> While that is an interesting idea that has been floated a few times, it has enough complexities and edge cases of its own to address that we feel it is out of scope.

While it certainly is your right as the RFC authors to consider certain
things out of scope for an RFC, I strongly oppose the notion of shipping
something that is strictly inferior and comes with obvious semantic
issues due to perceived complexity of another solution and then
following up with the proper solution that has already been identified.
As I've outlined in my previous emails, I found defining semantics for
an 'init' hook straight-forward when looking at how PHP works as of today.

8.

> However, this RFC is in no way incompatible with adding an init hook in the future should it be proposed.

This is true, but as I've mentioned before, an 'init' hook would enable
the same use cases without bringing along issues. So it really should be
"one of them, but not both" (with "one of them" being the init hook).

--------

After reading through the discussion, it seems the only argument against
the 'init' hook is perceived complexity. It is not at all clear to me
why this means that we must now rush something with clear issues into
PHP 8.5.

I'd understand the arguments you're pushing for if readonly were appropriate to build immutable objects. Yet that's not the case, so such reasoning is built on sand I'm sorry...

To me the RFC enables useful capabilities that authors are going to need. Or find workarounds for. Which means more ugliness to come...

I am failing to understand what capabilities are not going to be
addressed by an `init` hook, which some of us (if I'm allowed to speak
for us) seem to think is the correct approach here.

I have noticed in some discussions with my coworkers that it seems
that some people think that readonly implies a *contract about
writability to consumers of the class*, that is, it implies that
consumers of the class can only *read* the value, not write it. I
think I could understand why people that think this way would have no
problem with `get` hooks, since it still upholds what they think the
contract is. I feel, however, that that desired contract is actually
achieved by asymmetric visibility, e.g. public(get) protected(set),
which I view as orthogonal to readonly.

When I point out that asymmetric visibility exists, everyone I've
talked to so far agrees that readonly makes more sense as a *contract
that the value won't change*. And, as I've pointed out above, I
believe that is the intent of the original RFC text for the feature.

With this understanding of the contract of readonly, I struggle to
understand why an author *who wants to use readonly* would need `get`
hook capabilities and not in fact `init` hook capabilities. If they
needed generic `get` hook capabilities for a property (and not just
lazy loading), then my position is that *they don't actually want
readonly*.

On Fri, Jul 18, 2025, at 21:43, Eric Norris wrote:

Nick, Larry,

On Fri, Jul 18, 2025 at 2:01 PM Nicolas Grekas
<nicolas.grekas+php@gmail.com> wrote:

Le ven. 18 juil. 2025 à 18:32, Tim Düsterhus <tim@bastelstu.be> a écrit :

Hi

On 7/17/25 18:26, Larry Garfield wrote:

Given the lack of consensus both here and in off-list discussions on how to handle get hooks, we have done the following:

  • Split the RFC into two sections, one for get, one for set.
  • Expanded and refined the examples for both. The implementation is still the original, however.
  • Split the vote into two: one for allowing readonly get hooks, one for readonly set hooks.

We will start the vote sometime this weekend, most likely, unless some major feedback appears before then, and let the chips fall where they may.

After working through (most of) the discussion, I’ve now taken a look at
the updated RFC. I have the following remarks:

It is really “write-once”, which is not the same as immutable (as shown above). But there’s no reason that “write-once” need be incompatible with hooks.

This is a strawman argumentation, as I’ve outlined in my previous
emails, calling readonly “write-once” is wrong. It is a reasonable user
expectation to always get the identical value when reading a value that
may only be set once. By calling it “write-once” you are trying to shift
the focus to the write operation, which is totally irrelevant for user
expectations when interacting with readonly properties. Especially for
expectations of users that just use a class rather than writing one.

To my ears, write-once is more accurate than readonly because it sticks to the facts of how this behaves. That’s very relevant.
Using readonly to suggest immutable is where the arguments for rejecting this RFC are weak.
readonly doesn’t mean immutable, no matter how hard some want it to be…

(including a snippet from a separate email from Larry below)

Does readonly refer to the value returned? If so, that’s already been broken since the beginning because the property can be a mutable object, so trusting the data returned to be “the same” is already not safe.

It seems to me that the original intent of readonly was to mean
immutable, and points to a property always equaling itself in the
rationale section
(https://wiki.php.net/rfc/readonly_properties_v2#rationale):

$prop = $this->prop;
$fn(); // Any code may run here.
$prop2 = $this->prop;
assert($prop === $prop2); // Always holds.

It even calls out that this does not restrict interior mutability,
which I believe you are using to argue that it doesn’t actually mean
immutable:

“However, readonly properties do not preclude interior mutability.
Objects (or resources) stored in readonly properties may still be
modified internally.”

This is exactly how I think about readonly. The identity of property
won’t change, but the object itself might. Now if the object is also a
readonly class (and recursive for any of those class’s properties
that are objects), then you would truly have an immutability
guarantee of both the identity of the object and the object’s
properties. This is ignoring __get, which I have pointed out elsewhere
is worth ignoring, since we can conceivably remove that from readonly
classes if we pass init hooks.

I don’t want to accuse you of cherry-picking … but this is clearly cherry picking. From that same text:


It is worth noting that having a readonly property feature does not preclude introduction of accessors. C# supports both readonly properties and accessors. C# also provides properties with implicit backing storage through accessor syntax, but this is not the only way to do it. For example, Swift has special syntax for asymmetric visibility, rather than specifying visibility on implicitly implemented accessors.
Even if we have property accessors, I believe it may be worthwhile to limit them to computed properties only, and solve use-cases that involve engine-managed storage through other mechanisms, such as readonly properties and property-level asymmetric visibility. This avoids confusion relating to the two kinds of accessors (implicit and explicit), and also allows us to make their behavior independent of accessor constraints. For example, a first-class asymmetric visibility feature would shield the user from considering distinctions such as get; vs &get; accessors. These are externalities of the general accessor feature and not needed for asymmetric visibility.
A separate implementation can also be more efficient. After initialization, a readonly property will have the same performance characteristics as a normal property. Accessor-based properties, even with implicit storage, still carry a performance penalty.


The original author (Nikita) suggested that there’s nothing in the original design that precludes accessors – and highlights languages where there are both and they are doing just fine more than 5 years later.

[snip]

I am failing to understand what capabilities are not going to be
addressed by an init hook, which some of us (if I’m allowed to speak
for us) seem to think is the correct approach here.

I have noticed in some discussions with my coworkers that it seems
that some people think that readonly implies a contract about
writability to consumers of the class
, that is, it implies that
consumers of the class can only read the value, not write it. I
think I could understand why people that think this way would have no
problem with get hooks, since it still upholds what they think the
contract is. I feel, however, that that desired contract is actually
achieved by asymmetric visibility, e.g. public(get) protected(set),
which I view as orthogonal to readonly.

When I point out that asymmetric visibility exists, everyone I’ve
talked to so far agrees that readonly makes more sense as a contract
that the value won’t change
. And, as I’ve pointed out above, I
believe that is the intent of the original RFC text for the feature.

With this understanding of the contract of readonly, I struggle to
understand why an author who wants to use readonly would need get
hook capabilities and not in fact init hook capabilities. If they
needed generic get hook capabilities for a property (and not just
lazy loading), then my position is that they don’t actually want
readonly
.

I think an init hook is out of the question for 8.5, so I’m not even sure it’s worth discussing.

— Rob

Le 19 juil. 2025 à 00:41, Rob Landers rob@bottled.codes a écrit :

The original author (Nikita) suggested that there’s nothing in the original design that precludes accessors – and highlights languages where there are both and they are doing just fine more than 5 years later.

Hi Rob,

It is indeed entirely reasonable to have both readonly properties and hooked properties (aka accessors), and today PHP has indeed both of them (and even asymmetric visibility on top of that, as a separate feature contrarily to C#). But it doesn’t mean that it is reasonable for the same property to be both readonly and hooked, which is the point that is currently disputed. — What do the other languages allow? Is it possible to define a readonly property with a user-defined getter? (Disclaimer: Even if one of them allows such a thing, I’ll still think that it is a bad idea.)

—Claude

On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote:

Le 19 juil. 2025 à 00:41, Rob Landers rob@bottled.codes a écrit :

The original author (Nikita) suggested that there’s nothing in the original design that precludes accessors – and highlights languages where there are both and they are doing just fine more than 5 years later.

Hi Rob,

It is indeed entirely reasonable to have both readonly properties and hooked properties (aka accessors), and today PHP has indeed both of them (and even asymmetric visibility on top of that, as a separate feature contrarily to C#). But it doesn’t mean that it is reasonable for the same property to be both readonly and hooked, which is the point that is currently disputed. — What do the other languages allow? Is it possible to define a readonly property with a user-defined getter? (Disclaimer: Even if one of them allows such a thing, I’ll still think that it is a bad idea.)

—Claude

Hey Claude,

From what I’ve seen in other languages, this combination is fairly common and not inherently poblematic.

  • C# allows get hooks with user-defined logic, even in readonly structs.
  • Kotlin uses val with a get() body, which is readonly from the consumer’s perspective, even though the value is computed.
  • TypeScript allows a get-only accessor which acts readonly.
  • Swift also allows get-only computed accessors/hooks.

Most languages treat these as an abstraction boundary: allowing you to expose computed state while still guaranteeing external immutability.

This RFC is proposing something similar: the public surface is observably immutable, even if a value is derived. We can already do this today with more boilerplate:

class Foo {
public function __construct(private readonly int $_bar) {}
public int $bar { get => $this->_bar * 2; }
}

The RFC reduces that boilerplate and clarifies intent. In the example above, there is nothing mutable about it. It is “read only” from a public contract point-of-view, we just can’t mark it as a readonly class.

As for side-effects or non-determinism, that is a valid concern, but one that applies to any property hook and non-scalar property. A readonly declaration doesn’t necessarily encourage or prevent that, it simply declares that the instance state cannot be mutated after construction.

I’d argue the RFC aligns well with the conventions and capabilities of other languages, and builds on what PHP already allows.

— Rob

Le 19 juil. 2025 à 09:46, Rob Landers rob@bottled.codes a écrit :

On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote:

Le 19 juil. 2025 à 00:41, Rob Landers rob@bottled.codes a écrit :

The original author (Nikita) suggested that there’s nothing in the original design that precludes accessors – and highlights languages where there are both and they are doing just fine more than 5 years later.

Hi Rob,

It is indeed entirely reasonable to have both readonly properties and hooked properties (aka accessors), and today PHP has indeed both of them (and even asymmetric visibility on top of that, as a separate feature contrarily to C#). But it doesn’t mean that it is reasonable for the same property to be both readonly and hooked, which is the point that is currently disputed. — What do the other languages allow? Is it possible to define a readonly property with a user-defined getter? (Disclaimer: Even if one of them allows such a thing, I’ll still think that it is a bad idea.)

—Claude

Hey Claude,

From what I’ve seen in other languages, this combination is fairly common and not inherently poblematic.

  • C# allows get hooks with user-defined logic, even in readonly structs.
  • Kotlin uses val with a get() body, which is readonly from the consumer’s perspective, even though the value is computed.
  • TypeScript allows a get-only accessor which acts readonly.
  • Swift also allows get-only computed accessors/hooks.

Hi Rob,

The main problem is that we don’t agree on the meaning of “readonly property”.

I’ve check TypeScript:

  • It has getters and setters, which correspond to PHP get/set hooks without backing store.

  • Separately, it also has a readonly modifier that can be applied to properties; the semantics is that such a property may be initialised either at declaration or inside the constructor, but cannot be modified afterwards. That corresponds approximatively to PHP readonly properties.

  • But a “get-only accessor” is not the same thing as a “readonly property” in the specific sense of ”a property decorated with the readonly modifier”. Also, you cannot add the readonly modifier to a get accessor.

—Claude

On Sat, Jul 19, 2025, at 12:09, Claude Pache wrote:

Le 19 juil. 2025 à 09:46, Rob Landers rob@bottled.codes a écrit :

On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote:

Le 19 juil. 2025 à 00:41, Rob Landers rob@bottled.codes a écrit :

The original author (Nikita) suggested that there’s nothing in the original design that precludes accessors – and highlights languages where there are both and they are doing just fine more than 5 years later.

Hi Rob,

It is indeed entirely reasonable to have both readonly properties and hooked properties (aka accessors), and today PHP has indeed both of them (and even asymmetric visibility on top of that, as a separate feature contrarily to C#). But it doesn’t mean that it is reasonable for the same property to be both readonly and hooked, which is the point that is currently disputed. — What do the other languages allow? Is it possible to define a readonly property with a user-defined getter? (Disclaimer: Even if one of them allows such a thing, I’ll still think that it is a bad idea.)

—Claude

Hey Claude,

From what I’ve seen in other languages, this combination is fairly common and not inherently poblematic.

  • C# allows get hooks with user-defined logic, even in readonly structs.
  • Kotlin uses val with a get() body, which is readonly from the consumer’s perspective, even though the value is computed.
  • TypeScript allows a get-only accessor which acts readonly.
  • Swift also allows get-only computed accessors/hooks.

Hi Rob,

The main problem is that we don’t agree on the meaning of “readonly property”.

I’ve check TypeScript:

  • It has getters and setters, which correspond to PHP get/set hooks without backing store.

  • Separately, it also has a readonly modifier that can be applied to properties; the semantics is that such a property may be initialised either at declaration or inside the constructor, but cannot be modified afterwards. That corresponds approximatively to PHP readonly properties.

  • But a “get-only accessor” is not the same thing as a “readonly property” in the specific sense of ”a property decorated with the readonly modifier”. Also, you cannot add the readonly modifier to a get accessor.

—Claude

The error you get when trying to modify the “get-only accessor” is Error: Cannot assign to 'name' because it is a read-only property

Thus, readonly is implied by the get-only accessor, there’s no need to specify it directly.

— Rob

On Fri, Jul 18, 2025, at 14:10, Tim Düsterhus wrote:

Hi

Apologies for the belated reply. I was busy with getting my own
implementation wrapped up and the thread was so active that I had
troubles keeping up.

Hi Tim,

Thanks for taking the time to reply. That said, I would like to address a concern, not about the content of your message, but the timing.

On multiple RFCs, you’ve joined in once the discussions has wound down and a vote is immeninent. At this point, many participants have assumed the key issues are raised and addressed; or at least, reached the point of constructive impasse.

Reopening settled threads so close to vote tends to disrupt the process. It forces others to revisit old arguments under time pressure, giving the late comments disproportionate visibility, and risks stalling momentum.

I understand that threads move quickly and schedules vary, but if a concern is important enough to raise, it really helps to do so while the discussions are actively evolving. Otherwise, it becomes difficult to engage meaningfully.

At a certain point, late feedback stops being helpful and starts to erode the trust and rhythm of the process.

— Rob

Op zaterdag 19 juli 2025 schreef Rob Landers rob@bottled.codes:

On Fri, Jul 18, 2025, at 14:10, Tim Düsterhus wrote:

Hi
Apologies for the belated reply. I was busy with getting my own
implementation wrapped up and the thread was so active that I had
troubles keeping up.

Hi Tim,
Thanks for taking the time to reply. That said, I would like to address a concern, not about the content of your message, but the timing.
On multiple RFCs, you’ve joined in once the discussions has wound down and a vote is immeninent. At this point, many participants have assumed the key issues are raised and addressed; or at least, reached the point of constructive impasse.

Hey

To be honest, I find your email a bit strange, perhaps even misdirected. As someone who followed this discussion more quietly, it is absolutely not my impression that the discussion wounded down already.

Reopening settled threads so close to vote tends to disrupt the process. It forces others to revisit old arguments under time pressure, giving the late comments disproportionate visibility, and risks stalling momentum.

It wasn’t closed, so there isn’t anything to reopen. Also may I remind you that the call for an impeding vote is the thing that triggers time pressure, not Tim’s reply.
The goal of having an RFC discussion should be to get consensus during the discussion phase.

I understand that threads move quickly and schedules vary, but if a concern is important enough to raise, it really helps to do so while the discussions are actively evolving. Otherwise, it becomes difficult to engage meaningfully.

He did raise it.

Keeping track of the entire ML discussions is hard, and also difficult time-wise. Tim is one of the people who tries to participate to basically every RFC, doing his part in making sure we end up with the best possible outcome for a feature. I’d call that meaningful. I’d also rather delay a feature than having something sooner that we didn’t stand behind completely.

At a certain point, late feedback stops being helpful and starts to erode the trust and rhythm of the process.

I wouldn’t call it late.
Rushing this RFC to vote to get it into 8.5, despite there being no clear consensus, is the thing that erodes trust and breaks the rythm of the process.

— Rob

Kind regards
Niels

On 19. Jul 2025, at 22:46, Niels Dossche dossche.niels@gmail.com wrote:

Op zaterdag 19 juli 2025 schreef Rob Landers rob@bottled.codes:

On Fri, Jul 18, 2025, at 14:10, Tim Düsterhus wrote:

Hi
Apologies for the belated reply. I was busy with getting my own
implementation wrapped up and the thread was so active that I had
troubles keeping up.

Hi Tim,
Thanks for taking the time to reply. That said, I would like to address a concern, not about the content of your message, but the timing.
On multiple RFCs, you’ve joined in once the discussions has wound down and a vote is immeninent. At this point, many participants have assumed the key issues are raised and addressed; or at least, reached the point of constructive impasse.

Hey

To be honest, I find your email a bit strange, perhaps even misdirected. As someone who followed this discussion more quietly, it is absolutely not my impression that the discussion wounded down already.

Reopening settled threads so close to vote tends to disrupt the process. It forces others to revisit old arguments under time pressure, giving the late comments disproportionate visibility, and risks stalling momentum.

It wasn’t closed, so there isn’t anything to reopen. Also may I remind you that the call for an impeding vote is the thing that triggers time pressure, not Tim’s reply.
The goal of having an RFC discussion should be to get consensus during the discussion phase.

I understand that threads move quickly and schedules vary, but if a concern is important enough to raise, it really helps to do so while the discussions are actively evolving. Otherwise, it becomes difficult to engage meaningfully.

He did raise it.

Keeping track of the entire ML discussions is hard, and also difficult time-wise. Tim is one of the people who tries to participate to basically every RFC, doing his part in making sure we end up with the best possible outcome for a feature. I’d call that meaningful. I’d also rather delay a feature than having something sooner that we didn’t stand behind completely.

At a certain point, late feedback stops being helpful and starts to erode the trust and rhythm of the process.

I wouldn’t call it late.
Rushing this RFC to vote to get it into 8.5, despite there being no clear consensus, is the thing that erodes trust and breaks the rythm of the process.

— Rob

Kind regards
Niels

Hey Niels,

In before: I personally didn’t have a problem with Tim joining in late.

I, however, want to express that I don’t feel anything productive happens at this point.

My personal impression is, there wasn’t anything new added since the early beginning of the discussion.

Everything was brought up early on. If you believe that’s not the case and I missed something, please point me to the new arguments.

So, calling it “not wound down” feels off to me. And that’s why I answer now.

As someone who is a new participant here, please allow me to ask:

When is a discussion allowed to be considered wound down?

And, is repeating the same arguments (just by different persons) really a reason to keep a discussion going?

The whole controversy is about get.

We addressed this by switching to a split vote, because literally all those concerns/opinions (allow it; don’t allow it; add init instead; cache it; don’t cache it) can for now be “addressed” with a “no”-vote on get, and then follow up with a new get RFC later.

Am I wrong?

What is this all about now?

What are we doing now?

Do we keep repeating the same arguments, and disallow bringing this to vote at all? Even though like literally everyone seems to be on board with “set is ok”?

Or are we allowed to move on with a vote?

despite there being no clear consensus

The clear consensus seems to be that set should be allowed. That’s why we adjusted the vote.

I repeat: everyone with a problem (any kind) on get can vote “no” on get and “yes” on set.

Again, this is nothing specifically towards Tim.

Cheers,

Nick

On 20/07/2025 02:58, Nick wrote:

Hey Niels,

Hey Nick

In before: I personally didn’t have a problem with Tim joining in late.

I, however, want to express that I don’t feel anything productive happens at this point.

My personal impression is, there wasn’t anything new added since the early beginning of the discussion.

Everything was brought up early on. If you believe that’s not the case and I missed something, please point me to the new arguments.

No new arguments came as the concerns have been sent out earlier indeed.
Lately the discussion seems more geared towards the expectations of the users of readonly, and what that means for the set hook.

So, calling it “not wound down” feels off to me. And that’s why I answer now.

As someone who is a new participant here, please allow me to ask:

When is a discussion allowed to be considered wound down?

This has never been defined and always has a subjective part.
In general, no one will block you from bringing it to a vote if no substantial changes have been made to the RFC and the discussion lasted at least 2 weeks.

And, is repeating the same arguments (just by different persons) really a reason to keep a discussion going?

No, but it isn't bad to point out some things right before the vote.

The whole controversy is about `get`.

That's true.
Note though that the fact that the RFC still includes this does show a non-consensus from the authors PoV.
Either you fully stand behind your own RFC and wouldn't have split the vote, or you agreed that the get hook is a bad idea.
In the latter case, why even include this still in the RFC text, especially after Larry said he's positive that part won't pass?
This comes across as really wanting something of the RFC to pass, not aiming for the best "solution".

We addressed this by switching to a split vote, because literally all those concerns/opinions (allow it; don’t allow it; add `init` instead; cache it; don’t cache it) can _for now_ be “addressed” with a “no”-vote on `get`, and then follow up with a new `get` RFC later.

It's important to plan for the future and come up with a holistic solution.
I don't want to end up in a situation where in hindsight we shouldn't have allowed a "set hook" for example and should've just left readonly alone.

Am I wrong?

What is this all about now?

What are we doing now?

Do we keep repeating the same arguments, and disallow bringing this to vote at all? Even though like literally everyone seems to be on board with “`set` is ok”?

I believe I answered this, but just to make it extra clear: you are allowed to bring this to a vote, no one can veto you for that.
I don't believe everyone (who replied) is pro "set hook". And definitely not in other channels.

Or are we allowed to move on with a vote?

despite there being no clear consensus

The clear consensus seems to be that `set` should be allowed. That’s why we adjusted the vote.

I repeat: everyone with a problem (any kind) on `get` can vote “no” on `get` and “yes” on `set`.

I believe I answered this above already.

Again, this is nothing specifically towards Tim.

*Cheers,*

Nick

I'll end with saying that this should not discourage you from interacting with the ML.
You got a bit "unlucky" with the subjects you chose as both hooks and readonly are a bit controversial topics to begin with.

Kind regards
Niels

On 20. Jul 2025, at 15:54, Niels Dossche dossche.niels@gmail.com wrote:

On 20/07/2025 02:58, Nick wrote:

Hey Niels,

Hey Nick

Hey Nils,

The whole controversy is about get.

That’s true.
Note though that the fact that the RFC still includes this does show a non-consensus from the authors PoV.

Either you fully stand behind your own RFC and wouldn’t have split the vote, or you agreed that the get hook is a bad idea.

This is a misinterpretation.

In the latter case, why even include this still in the RFC text, especially after Larry said he’s positive that part won’t pass?
This comes across as really wanting something of the RFC to pass, not aiming for the best “solution”.

Yes, we really want the set part to pass.
No, this does not mean that we not aim for the best solution.
We listened to feedback and adjusted the voting structure accordingly.

Please don’t forget that some people here support the RFC as is.
As far as my understanding goes the list discussion is not a pre-vote.

The vote will show what the majority, including the silent part, wants.
There is, in my opinion, no need to patronise and prevent them from voting for what they want.

We addressed this by switching to a split vote, because literally all those concerns/opinions (allow it; don’t allow it; add init instead; cache it; don’t cache it) can for now be “addressed” with a “no”-vote on get, and then follow up with a new get RFC later.

It’s important to plan for the future and come up with a holistic solution.
I don’t want to end up in a situation where in hindsight we shouldn’t have allowed a “set hook” for example and should’ve just left readonly alone.

I honestly cannot come up with a reason for why this would be the case.

Am I wrong?

What is this all about now?

What are we doing now?

Do we keep repeating the same arguments, and disallow bringing this to vote at all? Even though like literally everyone seems to be on board with “set is ok”?

I believe I answered this, but just to make it extra clear: you are allowed to bring this to a vote, no one can veto you for that.
I don’t believe everyone (who replied) is pro “set hook”. And definitely not in other channels.

You asked to seek “clear consensus”. I am and was happy to take all opinions into account. So, I think I did that.
I, naturally, cannot take any opinions into account I am not aware of because they were expressed in “other channels”.
Here, on-list, everyone seems to be on board. And that’s what I can work with.

I’ll end with saying that this should not discourage you from interacting with the ML.

I appreciate the consideration! Everything is ok.
Not succeeding will not hurt my feelings, and I’ll stick around.

You got a bit “unlucky” with the subjects you chose as both hooks and readonly are a bit controversial topics to begin with.

In retrospective I am very aware of that! :sweat_smile:

Kind regards
Niels

Cheers,
Nick

Hi Rob,

I'm going to respond to a few points from earlier emails here instead
of each one.

On Sat, Jul 19, 2025 at 6:13 AM Rob Landers <rob@bottled.codes> wrote:

On Sat, Jul 19, 2025, at 12:09, Claude Pache wrote:

Le 19 juil. 2025 à 09:46, Rob Landers <rob@bottled.codes> a écrit :

On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote:

Le 19 juil. 2025 à 00:41, Rob Landers <rob@bottled.codes> a écrit :

The original author (Nikita) suggested that there's nothing in the original design that precludes accessors -- and highlights languages where there are both and they are doing just fine more than 5 years later.

Hi Rob,

It is indeed entirely reasonable to have both readonly properties and hooked properties (aka accessors), and today PHP has indeed both of them (and even asymmetric visibility on top of that, as a separate feature contrarily to C#). But it doesn’t mean that it is reasonable for the *same* property to be *both* readonly and hooked, which is the point that is currently disputed. — What do the other languages allow? Is it possible to define a readonly property with a user-defined getter? (Disclaimer: Even if one of them allows such a thing, I’ll still think that it is a bad idea.)

I do not believe I was cherry picking; I share Claude's interpretation
here that the RFC says that readonly doesn't prevent the language from
adopting accessors (hooks) later, which is what the language did, and
notably it did without applying them to readonly properties.

Could you perhaps walk me through your thinking when the RFC claims
that `assert($prop === $prop2)` "always holds"?

—Claude

Hey Claude,

From what I've seen in other languages, this combination is fairly common and not inherently poblematic.

- C# allows get hooks with user-defined logic, even in readonly structs.
- Kotlin uses val with a get() body, which is readonly from the consumer's perspective, even though the value is computed.
- TypeScript allows a get-only accessor which acts readonly.
- Swift also allows get-only computed accessors/hooks.

(disclaimer, I am not a C# expert)

It seems that C# has both fields and properties, and a readonly field
seems to align with what a few of us are claiming is how PHP readonly
properties should work. C# properties are more open-ended, and don't
actually support the readonly keyword - you can make them "read only"
in the sense of only having a get accessor (and you can mark that
accessor itself as readonly), but this is different from readonly
fields, which enforce a contract about the mutability of the field.

I think that C# fields, both readonly and not, match PHP's properties
without hooks. C# properties - which I believe cannot be *marked*
readonly, but they can be *made* read only, i.e. only exposing a get
accessor - match PHP's properties with hooks.

Hi Rob,

The main problem is that we don’t agree on the meaning of “readonly property”.

I agree with Claude here. Rob, I'm curious how you interpret the
contract of readonly. Do you think that it is a contract about
writability to consumers of the class? In an earlier email, you said:

In the example above, there is nothing mutable about it. It is "read only" from a public contract point-of-view, we just can't mark it as a readonly class.

In this most recent email, you said:

The error you get when trying to modify the "get-only accessor" is `Error: Cannot assign to 'name' because it is a read-only property`

Thus, readonly is implied by the get-only accessor, there's no need to specify it directly.

Would you say this is how you are interpreting readonly? That it is a
contract to consumers of the class that they can read but cannot write
to it?

(including a snippet from an earlier email)

I think an init hook is out of the question for 8.5, so I'm not even sure it's worth discussing.

I don't agree that it's not worth discussing alternative solutions to
the problems the RFC authors intend to solve. I think it has been
expressed elsewhere, but I believe we should take the time to make the
best decisions for the language and not rush to add a controversial
feature.

On Sun, Jul 20, 2025, at 3:54 AM, Niels Dossche wrote:

When is a discussion allowed to be considered wound down?

This has never been defined and always has a subjective part.
In general, no one will block you from bringing it to a vote if no
substantial changes have been made to the RFC and the discussion lasted
at least 2 weeks.

Indeed, and I've been yelled at in the past for making non-trivial changes to an RFC "too close" to when the vote is called. (For some undefined definition of "too close.")

Note though that the fact that the RFC still includes this does show a
non-consensus from the authors PoV.
Either you fully stand behind your own RFC and wouldn't have split the
vote, or you agreed that the get hook is a bad idea.
In the latter case, why even include this still in the RFC text,
especially after Larry said he's positive that part won't pass?
This comes across as really wanting something of the RFC to pass, not
aiming for the best "solution".

No, we split the vote because, as stated, based on the available evidence (this list) "set" appears to be uncontroversial, but "get" is. But ripping out half the RFC right before calling a vote would certainly qualify as a not-small change, and if we want to get the "set" portion into 8.5, we have a short window before a vote can be called. Hence, splitting the vote, which is the smaller change. Submitting essentially a new RFC at this point isn't really an option.

If by some miracle the 'get' hook also passes, I'm OK with that. But it's hardly the first vote that's been started despite considerable opposition.

I do not recall seeing anyone make a compelling argument against readonly 'set' hooks, so at the moment it does look to us like allowing set hooks, at least, is a "best solution." (Or at least, best presented so far.)

The idea that a split vote means the authors don't support their own RFC is nonsensical, given that multi-part votes or split votes or secondary votes come up multiple times every release. Rather, it means the different parts are related enough and small enough to be digestible in a single RFC, but each can stand on their own if necessary. In this case, nothing about the get hook necessitates a set hook, and nothing about the set hook necessitates the get hook.

--Larry Garfield

On Sun, Jul 20, 2025, at 19:18, Eric Norris wrote:

Hi Rob,

I’m going to respond to a few points from earlier emails here instead
of each one.

On Sat, Jul 19, 2025 at 6:13 AM Rob Landers <rob@bottled.codes> wrote:

On Sat, Jul 19, 2025, at 12:09, Claude Pache wrote:

Le 19 juil. 2025 à 09:46, Rob Landers <rob@bottled.codes> a écrit :

On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote:

Le 19 juil. 2025 à 00:41, Rob Landers <rob@bottled.codes> a écrit :

The original author (Nikita) suggested that there’s nothing in the original design that precludes accessors – and highlights languages where there are both and they are doing just fine more than 5 years later.

Hi Rob,

It is indeed entirely reasonable to have both readonly properties and hooked properties (aka accessors), and today PHP has indeed both of them (and even asymmetric visibility on top of that, as a separate feature contrarily to C#). But it doesn’t mean that it is reasonable for the same property to be both readonly and hooked, which is the point that is currently disputed. — What do the other languages allow? Is it possible to define a readonly property with a user-defined getter? (Disclaimer: Even if one of them allows such a thing, I’ll still think that it is a bad idea.)

I do not believe I was cherry picking; I share Claude’s interpretation
here that the RFC says that readonly doesn’t prevent the language from
adopting accessors (hooks) later, which is what the language did, and
notably it did without applying them to readonly properties.

Could you perhaps walk me through your thinking when the RFC claims
that assert($prop === $prop2) “always holds”?

Hi Eric,

I think that is explaining how Nikita arrived at some of the conclusions (it is in the rationale section, after all) and shouldn’t be taken as literal. Here are some snippets that I think should be looked at a little more closely, that seem to align with the vision of the feature, in the context of getters/setters:

“The closest alternative is to declare the property private, and only expose a public getter” – indicates to me that a single public getter should, in fact, be considered readonly.

“Support for first-class readonly properties allows you to directly expose public readonly properties, without fear that class invariants could be broken through external modification” – indicates to me that interior mutability (and maybe nondeterminism) should be at the discretion of the interior, not the exterior contract.

“…readonly properties do not preclude interior mutability. Objects (or resources) stored in readonly properties may still be modified internally” – further specifies that interior mutability is allowed; only exterior mutability isn’t.

It doesn’t define “interior mutability” stringently, so we can have differing opinions on that; but it seems to be “the value inside the property” which may or may not be an object, resource, or a hook’s result.

—Claude

Hey Claude,

From what I’ve seen in other languages, this combination is fairly common and not inherently poblematic.

  • C# allows get hooks with user-defined logic, even in readonly structs.
  • Kotlin uses val with a get() body, which is readonly from the consumer’s perspective, even though the value is computed.
  • TypeScript allows a get-only accessor which acts readonly.
  • Swift also allows get-only computed accessors/hooks.

(disclaimer, I am not a C# expert)

It seems that C# has both fields and properties, and a readonly field
seems to align with what a few of us are claiming is how PHP readonly
properties should work. C# properties are more open-ended, and don’t
actually support the readonly keyword - you can make them “read only”
in the sense of only having a get accessor (and you can mark that
accessor itself as readonly), but this is different from readonly
fields, which enforce a contract about the mutability of the field.

I think that C# fields, both readonly and not, match PHP’s properties
without hooks. C# properties - which I believe cannot be marked
readonly, but they can be made read only, i.e. only exposing a get
accessor - match PHP’s properties with hooks.

I would prefer to simply allow specifying a class as readonly so long as only get hooks are present. However, I’m ok with saying that get hooks may themselves be readonly which accomplishes the same thing and is easier to reason about.

Hi Rob,

The main problem is that we don’t agree on the meaning of “readonly property”.

I agree with Claude here. Rob, I’m curious how you interpret the
contract of readonly. Do you think that it is a contract about
writability to consumers of the class? In an earlier email, you said:

In the example above, there is nothing mutable about it. It is “read only” from a public contract point-of-view, we just can’t mark it as a readonly class.

In this most recent email, you said:

The error you get when trying to modify the “get-only accessor” is Error: Cannot assign to 'name' because it is a read-only property

Thus, readonly is implied by the get-only accessor, there’s no need to specify it directly.

Would you say this is how you are interpreting readonly? That it is a
contract to consumers of the class that they can read but cannot write
to it?

That’s the definition in the RFC, from my reading of it: the ability to expose bare properties without worrying that someone else will modify it. One might argue that there really isn’t a reason for readonly anymore, since we have aviz + hooks. Or rather, that readonly could be just a shorthand for that – plus the ability to write to those properties exactly-once. I suspect this is where “write-once” is coming from elsewhere in the thread, since that is the only difference between “manual readonly” and “engine-powered readonly”.

(including a snippet from an earlier email)

I think an init hook is out of the question for 8.5, so I’m not even sure it’s worth discussing.

I don’t agree that it’s not worth discussing alternative solutions to
the problems the RFC authors intend to solve. I think it has been
expressed elsewhere, but I believe we should take the time to make the
best decisions for the language and not rush to add a controversial
feature.

Would an init hook actually solve it though? An init hook would basically just be a constructor / default value outside of the constructor that specifies an instance-level constant. The readonly property RFC goes into some details here:

“As the default value counts as an initializing assignment, a readonly property with a default value is essentially the same as a constant, and thus not particularly useful. The notion could become more useful in the future, if new expressions are allowed as property default values. At the same time, depending on how exactly property initialization would work in that case, having a default value on a readonly property could preclude userland serialization libraries from working, as they would not be able to replace the default-constructed object. Whether or not this is a concern depends on whether the property is initialized at time of object creation, or as an implicit part of the constructor (or similar). As these are open questions, the conservative choice is to forbid default values until these questions are resolved.”

When would the init hook get called? And in what order? This brings back some memories of solving some Java language bugs where static variables wouldn’t be initialized in time (they’re non-deterministic) causing strange crashes, or the potential to “deadlock” yourself and you need to read properties in a specific order in order to ensure the object gets initialized correctly.

This isn’t something that can be solved in a few weeks. Someone(s) needs to sit down and think through all the possibilities and then create a definition of an init hook that is better than a constructor.

— Rob

Hey all,

It’s important to plan for the future and come up with a holistic solution.
I don’t want to end up in a situation where in hindsight we shouldn’t have allowed a “set hook” for example and should’ve just left readonly alone.

I honestly cannot come up with a reason for why this would be the case.

@Niels
I saw you voted “no” for set.
I double checked the full RFC discussion. You didn’t participate at all until the very end.
Both mails were on a meta-level. None one your mails had any arguments which would justify your vote.
I don’t understand your vote. Would you mind to elaborate?

@Tim

I saw you voted “no” for get (expected, and understandable), and decided to be abstinent to set.
I double checked the full RFC discussion.
You asked four times explicitly, and one time indirectly, to allow set but not get.
Would you mind to elaborate why you decided to not vote “yes” for what you asked for?

As a new participant, I have difficulties to understand these kind of “politics” here.
What was this six weeks discussion exactly for if decisions apparently are taken in “other channels” that are not the officially documented ones?

Thank you.

Cheers,
Nick

On 21/07/2025 10:29, Nick wrote:

@Niels
I saw you voted “no” for `set`.
I double checked the full RFC discussion. You didn’t participate at all until the very end.
Both mails were on a meta-level. None one your mails had any arguments which would justify your vote.
I don’t understand your vote. Would you mind to elaborate?

Hi Nick

The vote for me was lost at the proposal itself, not the discussion.
Property hooks left a bit of a bitter aftertaste for me, we're still fixing opcache bugs regarding property hooks today,
and we had a particularly bad bug with the JIT where the JIT made some assumptions that were broken because hooks could override properties.
We also regularly get reports on php-src of people being confused by some of the behaviours of hooks.
Combine that with readonly, another complex feature, and I find it hard to be in favour of any of this.
While I think it's still a great feature, it also proved to be much more complex than initially thought, and it also shows that features
interact in unexpected ways with each other.

For me, the mental model of readonly is already complicated, and the hooks are too, combine the two and you get IMO unintuitive behaviour
regarding immutability etc. Anyway, this is not a new argument.

As a new participant, I have difficulties to understand these kind of “politics” here.

For me there is no politics at play regarding my vote.
For a large chunk of RFCs, I actually don't even vote.
For this one, I decided long ago.

What was this six weeks discussion exactly for if decisions apparently are taken in “other channels” that are not the officially documented ones?

The decision for me was made on my own.
There was one brief discussion in the PHPF Slack that Larry started, I briefly joined in there and there I told him about the mental model issue that I described above.
A few others also were not happy with the complicated mental model.

Thank you.

*Cheers,*
Nick

Kind regards
Niels

Hey Niels,

On 21. Jul 2025, at 15:56, Niels Dossche dossche.niels@gmail.com wrote:

On 21/07/2025 10:29, Nick wrote:

@Niels
I saw you voted “no” for set.
I double checked the full RFC discussion. You didn’t participate at all until the very end.
Both mails were on a meta-level. None one your mails had any arguments which would justify your vote.
I don’t understand your vote. Would you mind to elaborate?

Hi Nick

The vote for me was lost at the proposal itself, not the discussion.
Property hooks left a bit of a bitter aftertaste for me, we’re still fixing opcache bugs regarding property hooks today,
and we had a particularly bad bug with the JIT where the JIT made some assumptions that were broken because hooks could override properties.
We also regularly get reports on php-src of people being confused by some of the behaviours of hooks.
Combine that with readonly, another complex feature, and I find it hard to be in favour of any of this.
While I think it’s still a great feature, it also proved to be much more complex than initially thought, and it also shows that features
interact in unexpected ways with each other.

For me, the mental model of readonly is already complicated, and the hooks are too, combine the two and you get IMO unintuitive behaviour
regarding immutability etc. Anyway, this is not a new argument.

Thank you.

Cheers,
Nick

Kind regards
Niels

Thanks for the detailed answer–I get it.
I honestly would have appreciated to see you hop in with that in the earlier state of the RFC.
It would have been more productive from my point of view. Maybe next time!

As a new participant, I have difficulties to understand these kind of “politics” here.

For me there is no politics at play regarding my vote.
For a large chunk of RFCs, I actually don’t even vote.
For this one, I decided long ago.

What was this six weeks discussion exactly for if decisions apparently are taken in “other channels” that are not the officially documented ones?

The decision for me was made on my own.
There was one brief discussion in the PHPF Slack that Larry started, I briefly joined in there and there I told him about the mental model issue that I described above.
A few others also were not happy with the complicated mental model.

Neve mind me then. The “other channels “ argument sounded weird to me because I had no idea what it means. My bad.

Thank you.

Cheers,
Nick

On 21/07/2025 11:11, Nick wrote:

Thanks for the detailed answer--I get it.
I honestly would have appreciated to see you hop in with that in the earlier state of the RFC.
It would have been more productive from my point of view. Maybe next time!

Hi Nick.

Honestly, it would've been extremely challenging to convince me anyway...
In any case, I'll make a mental note for that to put this argument out more upfront earlier.

As a new participant, I have difficulties to understand these kind of “politics” here.

For me there is no politics at play regarding my vote.
For a large chunk of RFCs, I actually don't even vote.
For this one, I decided long ago.

What was this six weeks discussion exactly for if decisions apparently are taken in “other channels” that are not the officially documented ones?

The decision for me was made on my own.
There was one brief discussion in the PHPF Slack that Larry started, I briefly joined in there and there I told him about the mental model issue that I described above.
A few others also were not happy with the complicated mental model.

Neve mind me then. The “other channels “ argument sounded weird to me because I had no idea what it means. My bad.

I guess I should've been clearer. We're good though.

In any case, good luck with any future endeavors in PHP(-src).

--

Thank you.

*Cheers,*
Nick

Kind regards
Niels