[RFC] Clone with v2

php.internals

Volker Dusch

1 year ago
Hi internals, A couple of weeks ago, we stumbled over the fact that the original "clone with" RFC was abandoned and thought it would still be useful to suggest a trimmed down version of the proposal. The main goal of this RFC is to propose a lightweight, low friction implementation of this feature to satisfy the couple of use cases we see and "round out" PHP in how it handles cloning, especially around read-only-properties. https://wiki.php.net/rfc/clone_with_v2 We are actively looking for some feedback on https://wiki.php.net/rfc/clone_with_v2#open_issues, but of course all points are welcome. If this turns out to be more complex than anticipated, for some reason, specifically around syntax or BC implications, we'd rather not add this at all. A preliminary implementation that shows how small the change set needs to be exists at https://github.com/TimWolla/php-src/pull/6 Kind Regards, Tim and Volker
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Larry Garfield

1 year ago
On Wed, May 14, 2025, at 8:04 AM, Volker Dusch wrote:
> Hi internals, > > A couple of weeks ago, we stumbled over the fact that the original > "clone with" RFC was abandoned and thought it would still be useful to > suggest a trimmed down version of the proposal. > > The main goal of this RFC is to propose a lightweight, low friction > implementation of this feature to satisfy the couple of use cases we > see and "round out" PHP in how it handles cloning, especially around > read-only-properties. > > https://wiki.php.net/rfc/clone_with_v2 > > We are actively looking for some feedback on > https://wiki.php.net/rfc/clone_with_v2#open_issues, but of course all > points are welcome. > > If this turns out to be more complex than anticipated, for some reason, > specifically around syntax or BC implications, we'd rather not add this > at all. > > A preliminary implementation that shows how small the change set needs > to be exists at https://github.com/TimWolla/php-src/pull/6 > > Kind Regards, > Tim and Volker
As discussed off list, I really like this approach. It's definitely cleaner than the earlier versions.
> A magic __clone() method will be called before the new properties are assigned.
Why before and not after? I could probably make a good argument either direction, but we should be explicit about why we're making whatever decision. The last example, on readonly, is a bit confusing. It looks like it should work, but the comments say "but if we did this other thing we'd get this error." Just make it two separate classes and show one works and one doesn't. That makes it easier to follow.
> Alternatively, we could drop the variadic syntax and instead only accept an array as the second parameter. We're looking for feedback here in the discussion.
Oh lord please no. :-) Not using an array here is what I like about this syntax. An __object parameter is fine with me as a workaround. One shouldn't be using a named argument for that anyway. --Larry Garfield

Volker Dusch

1 year ago
On Wed, May 14, 2025 at 5:40 PM Larry Garfield <larry@garfieldtech.com> wrote:
>> A magic __clone() method will be called before the new properties are
assigned.
> Why before and not after? I could probably make a good argument either
direction, but we should be explicit about why we're making whatever decision. From a user-facing perspective, calling __clone() before makes it easy to know which state the object is in; all parameters are how they've been before the clone operation started and are therefore consistent. When calling __clone() afterward access to the new properties would already be set and the old ones gone, making this potentially harder to work with and harder to adapt existing implementations. So we chose the one that causes fewer potential issues and gotchas.
> The last example, on readonly, is a bit confusing. It looks like it
should work, but the comments say "but if we did this other thing we'd get this error." Just make it two separate classes and show one works and one doesn't. That makes it easier to follow. I've added the output to the var_dump statements to show what works. Standalone examples for the "missing" (set) felt more confusing than showing the difference in context and because its behavior is unaffected by the RFC and mostly documented in case someone isn't familiar with this detail in PHP, which I assume readers here will be.
>> Alternatively, we could drop the variadic syntax and instead only accept
an array as the second parameter. We're looking for feedback here in the discussion.
> Oh lord please no. :-) Not using an array here is what I like about this
syntax. An __object parameter is fine with me as a workaround. One shouldn't be using a named argument for that anyway. Thank you for your feedback! I'm looking forward to hearing what other people say as well, but it's nice to get a range of voices giving input here :) Let me just reiterate here that I find the difference to be minor to negligible. IDE/Tooling support needs to be built explicitly either way and is not harder or easier to do, and the ergonomics are quite unaffected in my opinion. So the worst outcome, for me, would be that we get hung up on the syntax here.
> One shouldn't be using a named argument for that anyway.
I'm not sure what you mean by that. Just so we're clear: `...["__object" => "foo"]` would also not work with the current implementation, as that's the same thing as a `__object: $value` named argument. (Should we decide to name the first parameter __object here). Sorry if I'm misunderstanding. Kind Regards, Volker
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Larry Garfield

1 year ago
On Wed, May 14, 2025, at 2:54 PM, Volker Dusch wrote:
> On Wed, May 14, 2025 at 5:40 PM Larry Garfield <larry@garfieldtech.com> wrote: >>> A magic __clone() method will be called before the new properties are assigned. >> Why before and not after? I could probably make a good argument either direction, but we should be explicit about why we're making whatever decision. > > From a user-facing perspective, calling __clone() before makes it easy > to know which state the object is in; all parameters are how they've > been before the clone operation started and are therefore consistent. > When calling __clone() afterward access to the new properties would > already be set and the old ones gone, making this potentially harder to > work with and harder to adapt existing implementations. So we chose the > one that causes fewer potential issues and gotchas.
Please include this in the RFC.
> Let me just reiterate here that I find the difference to be minor to > negligible. IDE/Tooling support needs to be built explicitly either way > and is not harder or easier to do, and the ergonomics are quite > unaffected in my opinion. So the worst outcome, for me, would be that > we get hung up on the syntax here.
I think the current syntax is optimal.
>> One shouldn't be using a named argument for that anyway. > > I'm not sure what you mean by that. Just so we're clear: > `...["__object" => "foo"]` would also not work with the current > implementation, as that's the same thing as a `__object: $value` named > argument. (Should we decide to name the first parameter __object here). > Sorry if I'm misunderstanding.
The concern is someone calling clone($foo, object: new Thingie()), which would complain that "object" is defined twice. Making the first argument named __object sidesteps that issue. If someone then calls clone(prop: 'new val', __object: $foo), that... I suppose would technically compile, but one should never do that, and I am perfectly happy if that breaks. Similar for if someone called clone(prop: 'new val', object: $foo) if we didn't rename that parameter. No one should be doing that and I am fine if that breaks. --Larry Garfield

Volker Dusch

1 year ago
On Thu, May 15, 2025 at 12:10 AM Larry Garfield <larry@garfieldtech.com> wrote:
> Please include this in the RFC.
Done
> The concern is someone calling clone($foo, object: new Thingie()), which
would complain that "object" is defined twice. Making the first argument named __object sidesteps that issue
> If someone then calls clone(prop: 'new val', __object: $foo), that... I
suppose would technically compile, but one should never do that, and I am perfectly happy if that breaks. Similar for if someone called clone(prop: 'new val', object: $foo) if we didn't rename that parameter. No one should be doing that and I am fine if that breaks Yes. I think we're on the same page here. But just to be clear, I'll restate it again with my own words: Whatever the first parameter (the to-be-cloned object) of the clone() function is called will be completely inaccessible as a property name. clone($foo, __object: "bar") will fail (Named parameter overwrites previous argument) clone($foo, ...["__object" => "bar"]) will also fail. While `clone(prop: 'new val', __object: $foo)` just works. For example, using the current implementation where the parameter is named `object`: ``` class Foo { public int $bar; } $x = new Foo(); var_dump(clone(bar: 5, object: $x)); // object(Foo)#2 (1) { // ["bar"]=> // int(5) // } ```
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Dmitry Derepko

1 year ago

Matthew Weier O'Phinney

1 year ago
On Wed, May 14, 2025 at 8:06 AM Volker Dusch <volker@tideways-gmbh.com> wrote:
> A couple of weeks ago, we stumbled over the fact that the original "clone > with" RFC was abandoned and thought it would still be useful to suggest a > trimmed down version of the proposal. > > The main goal of this RFC is to propose a lightweight, low friction > implementation of this feature to satisfy the couple of use cases we see > and "round out" PHP in how it handles cloning, especially around > read-only-properties. > > https://wiki.php.net/rfc/clone_with_v2 > > We are actively looking for some feedback on > https://wiki.php.net/rfc/clone_with_v2#open_issues, but of course all > points are welcome. >
The only question that arose for me is: what happens if a property name is provided to `clone()` that does not exist in the class definition; what will be the behavior at that time? Will an exception or error be thrown? If so, will it be a new one, or an existing one?
-- Matthew Weier O'Phinney mweierophinney@gmail.com https://mwop.net/ he/him

Tim Düsterhus

1 year ago
Hi Am 2025-05-14 22:06, schrieb Matthew Weier O'Phinney:
> The only question that arose for me is: what happens if a property name > is > provided to `clone()` that does not exist in the class definition; what > will be the behavior at that time? Will an exception or error be > thrown? If > so, will it be a new one, or an existing one?
It's mentioned in the "Technical details" section:
> Property assignments are made just as a regular assignment would be
It literally goes through the same code path. Thus a dynamic property will be created (and the associated warnings triggered). You can also see that in test `Zend/tests/clone/clone_with_002.phpt` of the implementation:
> Deprecated: Creation of dynamic property C::$c is deprecated in %s on > line %d
The internal implementation is roughly equivalent to: $cloned = clone $object; foreach ($withProperties as $key => $value) { $cloned->{$key} = $value; } return $cloned; Just with the exception that writing readonly properties is allowed on the clone (unless already touched by `__clone()`). Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Wed, May 14, 2025, at 4:00 PM, Tim Düsterhus wrote:
> The internal implementation is roughly equivalent to: > > $cloned = clone $object; > foreach ($withProperties as $key => $value) { > $cloned->{$key} = $value; > } > return $cloned; > > Just with the exception that writing readonly properties is allowed on > the clone (unless already touched by `__clone()`).
Subtle point here. If the __clone() method touches a readonly property, does that make the property inaccessible to the new clone-with? Or is it a separate "unlock scope"? I would expect the latter. A single unlock block would be confusing to me. --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-05-15 00:04, schrieb Larry Garfield:
> Subtle point here. If the __clone() method touches a readonly > property, does that make the property inaccessible to the new > clone-with?
Yes. Quoting from the RFC:
> The currently linked implementation “locks” a property if it modified > within __clone(), if this is useful is up for debate.
-
> A single unlock block would be confusing to me.
We’ve implemented it like that, because it felt most in line with what was decided in https://wiki.php.net/rfc/readonly_amendments#proposal_2readonly_properties_can_be_reinitialized_during_cloning, which says:
> Reinitialization of each property is possible once and only once:
We expect “public(set) readonly” + “__clone()” to be rare and from within the class, the author knows how their `__clone()` implementation works and can make sure it is compatible with whatever properties they might want to update during cloning. The lack of “use cases” is the primary reason we made the more conservative choice, but we are not particularly attached to this specific behavior. Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Thu, May 15, 2025, at 8:53 AM, Tim Düsterhus wrote:
> Hi > > Am 2025-05-15 00:04, schrieb Larry Garfield: >> Subtle point here. If the __clone() method touches a readonly >> property, does that make the property inaccessible to the new >> clone-with? > > Yes. Quoting from the RFC: > >> The currently linked implementation “locks” a property if it modified >> within __clone(), if this is useful is up for debate. > > - > >> A single unlock block would be confusing to me. > > We’ve implemented it like that, because it felt most in line with what > was decided in > https://wiki.php.net/rfc/readonly_amendments#proposal_2readonly_properties_can_be_reinitialized_during_cloning, > which says: > >> Reinitialization of each property is possible once and only once: > > We expect “public(set) readonly” + “__clone()” to be rare and from > within the class, the author knows how their `__clone()` implementation > works and can make sure it is compatible with whatever properties they > might want to update during cloning. The lack of “use cases” is the > primary reason we made the more conservative choice, but we are not > particularly attached to this specific behavior. > > Best regards > Tim Düsterhus
Fair. I could probably think of a use case if I tried hard, but I can't think of one off hand. It's just surprising in the abstract. I suppose making it a single unlock scope leaves open the option to split it in two later, but the reverse is not true. I won't fight for this one, just noting it as surprising. --Larry Garfield

Nicolas Grekas

1 year ago
Le jeu. 15 mai 2025 à 15:55, Tim Düsterhus <tim@bastelstu.be> a écrit :
> Hi > > Am 2025-05-15 00:04, schrieb Larry Garfield: > > Subtle point here. If the __clone() method touches a readonly > > property, does that make the property inaccessible to the new > > clone-with? > > Yes. Quoting from the RFC: > > > The currently linked implementation “locks” a property if it modified > > within __clone(), if this is useful is up for debate. > > - > > > A single unlock block would be confusing to me. > > We’ve implemented it like that, because it felt most in line with what > was decided in > > https://wiki.php.net/rfc/readonly_amendments#proposal_2readonly_properties_can_be_reinitialized_during_cloning, > > which says: > > > Reinitialization of each property is possible once and only once: > > We expect “public(set) readonly” + “__clone()” to be rare and from > within the class, the author knows how their `__clone()` implementation > works and can make sure it is compatible with whatever properties they > might want to update during cloning. The lack of “use cases” is the > primary reason we made the more conservative choice, but we are not > particularly attached to this specific behavior. > >
Being able to update a readonly property even if __clone already touched it looks critical to me because otherwise, it'd mean that adding a __clone method after publishing a first version of some class that has no __clone method would be a BC break. Nicolas

Volker Dusch

1 year ago
On Fri, May 16, 2025 at 9:41 PM Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote: Being able to update a readonly property even if __clone already touched it
> looks critical to me because otherwise, it'd mean that adding a __clone > method after publishing a first version of some class that has no __clone > method would be a BC break. >
Great input. Thank you Nicolas. While this only applies to `public public(set) readonly` protected readonly properties that are also then *touched* in the new clone method, it is indeed an E_FATAL with the current implementation. For these cases, that would indeed be an annoying gotcha, even if I don't have an example at hand, it might make sense to me to account for it. I'll update the RFC and publish a changelog on Monday, and I'll mention that there once I had another look at the implementation.
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Volker Dusch

1 year ago
I forgot to mention that on the flip side, this would allow `public public(set) readonly` properties to be "overwritten" after __clone touched them; which is why we chose the more "locked down" version in the first place. So I want to consider this before just updating the RFC :)

Volker Dusch

1 year ago
On Wed, May 14, 2025 at 10:06 PM Matthew Weier O'Phinney < mweierophinney@gmail.com> wrote:
> The only question that arose for me is: what happens if a property name
is provided to `clone()` that does not exist in the class definition; what will be the behavior at that time? Will an exception or error be thrown? If so, will it be a new one, or an existing one? Thank you for the question. I had that one myself looking at the initial implementation and forgot to write that down more explicitly. Tim already answered and linked the test, but I've also added an example to the RFC.
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Stephen Reay

1 year ago
> On 15 May 2025, at 06:26, Volker Dusch <volker@tideways-gmbh.com> wrote: > > On Wed, May 14, 2025 at 10:06 PM Matthew Weier O'Phinney <mweierophinney@gmail.com <mailto:mweierophinney@gmail.com>> wrote: > > > The only question that arose for me is: what happens if a property name is provided to `clone()` that does not exist in the class definition; what will be the behavior at that time? Will an exception or error be thrown? If so, will it be a new one, or an existing one? > > Thank you for the question. I had that one myself looking at the initial implementation and forgot to write that down more explicitly. > > Tim already answered and linked the test, but I've also added an example to the RFC. > > -- > Volker Dusch > Head of Engineering > Tideways GmbH > Königswinterer Str. 116 > 53227 Bonn > https://tideways.io/imprint > > Sitz der Gesellschaft: Bonn > Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) > Registergericht: Amtsgericht Bonn, HRB 22127
I may be missing something here.. So far the issues are "how do we deal with a parameter for the actual object, vs new properties to apply", "should __clone be called before or after the changes" and "this won't allow regular readonly properties to be modified". Isn't the previous suggestion of passing the new property arguments directly to the __clone method the obvious solution to all three problems? There's no potential for a conflicting property name, the developer can use the new property values in the order they see fit relative to the logic in the __clone call, and it's inherently in scope to write to any (unlocked during __clone) readonly properties. Cheers Stephen

Andreas Hennings

1 year ago
On Thu, 15 May 2025 at 08:24, Stephen Reay <php-lists@koalephant.com> wrote: [..]
> > > I may be missing something here.. > > So far the issues are "how do we deal with a parameter for the actual object, vs new properties to apply", "should __clone be called before or after the changes" and "this won't allow regular readonly properties to be modified". > > Isn't the previous suggestion of passing the new property arguments directly to the __clone method the obvious solution to all three problems?
What exactly should happen then? Would the __clone() method be responsible for assigning those properties? Or does the __clone() method get the chance to alter the values before they are assigned? (this would mean they have to be passed by reference) I think this last option is the best, because the values in the array can be changed without any readonly constraints. Another option I was thinking of would be to call __clone() after the changes are applied, and pass both the original object and the array of changes as first parameter. But I think this is a dead end. -- Andreas

Stephen Reay

1 year ago
> On 15 May 2025, at 16:44, Andreas Hennings <andreas@dqxtech.net> wrote: > > On Thu, 15 May 2025 at 08:24, Stephen Reay <php-lists@koalephant.com> wrote: > [..] >> >> >> I may be missing something here.. >> >> So far the issues are "how do we deal with a parameter for the actual object, vs new properties to apply", "should __clone be called before or after the changes" and "this won't allow regular readonly properties to be modified". >> >> Isn't the previous suggestion of passing the new property arguments directly to the __clone method the obvious solution to all three problems? > > What exactly should happen then? > Would the __clone() method be responsible for assigning those properties? > Or does the __clone() method get the chance to alter the values before > they are assigned? > (this would mean they have to be passed by reference) > I think this last option is the best, because the values in the array > can be changed without any readonly constraints. > > Another option I was thinking of would be to call __clone() after the > changes are applied, and pass both the original object and the array > of changes as first parameter. > But I think this is a dead end. > > -- Andreas > >> >> There's no potential for a conflicting property name, the developer can use the new property values in the order they see fit relative to the logic in the __clone call, and it's inherently in scope to write to any (unlocked during __clone) readonly properties. >> >> >> >> Cheers >> >> Stephen >> >> >> >
I would suggest that the __clone method should be directly responsible for making any changes, just as it is now when it comes to deep cloning or resetting values. Yes I realise it means developers need to then opt in and provide the functionality to support `clone $foo with(bar: "baz")` or whatever syntax is used. If the properties are public properties, there's nothing stopping someone writing their own clone_with() in userland now; If someone is using readonly properties I'd suggest they want to specifically manage updates to those properties themselves anyway. Additionally it means "clone with" would be usable for non-public properties at the discretion of the developer writing their code. The mental model is also very clear with this: copy the object in memory, and then call __clone(), with the arguments passed to the clone action - which may be none in the case of code that doesn't accept any clone arguments. The only change from the current model is that it *may* be passing arguments. Cheers Stephen

Rob Landers

1 year ago
On Thu, May 15, 2025, at 13:56, Stephen Reay wrote:
> > > > > On 15 May 2025, at 16:44, Andreas Hennings <andreas@dqxtech.net> wrote: > > > > On Thu, 15 May 2025 at 08:24, Stephen Reay <php-lists@koalephant.com> wrote: > > [..] > >> > >> > >> I may be missing something here.. > >> > >> So far the issues are "how do we deal with a parameter for the actual object, vs new properties to apply", "should __clone be called before or after the changes" and "this won't allow regular readonly properties to be modified". > >> > >> Isn't the previous suggestion of passing the new property arguments directly to the __clone method the obvious solution to all three problems? > > > > What exactly should happen then? > > Would the __clone() method be responsible for assigning those properties? > > Or does the __clone() method get the chance to alter the values before > > they are assigned? > > (this would mean they have to be passed by reference) > > I think this last option is the best, because the values in the array > > can be changed without any readonly constraints. > > > > Another option I was thinking of would be to call __clone() after the > > changes are applied, and pass both the original object and the array > > of changes as first parameter. > > But I think this is a dead end. > > > > -- Andreas > > > >> > >> There's no potential for a conflicting property name, the developer can use the new property values in the order they see fit relative to the logic in the __clone call, and it's inherently in scope to write to any (unlocked during __clone) readonly properties. > >> > >> > >> > >> Cheers > >> > >> Stephen > >> > >> > >> > > > > I would suggest that the __clone method should be directly responsible for making any changes, just as it is now when it comes to deep cloning or resetting values. > > Yes I realise it means developers need to then opt in and provide the functionality to support `clone $foo with(bar: "baz")` or whatever syntax is used. > > If the properties are public properties, there's nothing stopping someone writing their own clone_with() in userland now; If someone is using readonly properties I'd suggest they want to specifically manage updates to those properties themselves anyway. > > Additionally it means "clone with" would be usable for non-public properties at the discretion of the developer writing their code. > > The mental model is also very clear with this: copy the object in memory, and then call __clone(), with the arguments passed to the clone action - which may be none in the case of code that doesn't accept any clone arguments. The only change from the current model is that it *may* be passing arguments. > > Cheers > > Stephen >
When I was working on Records, one of the important things was the ability to "hook into" the update that was about to happen. For example, if you have a Money type, you'd want to be able to ensure it cannot be negative when updating via `with()`. This is super important for ensuring constraints are met during the clone. — Rob

Tim Düsterhus

1 year ago
Hi Am 2025-05-15 14:14, schrieb Rob Landers:
> For example, if you have a Money type, you'd want to be able to ensure > it cannot be negative when updating via `with()`. This is super > important for ensuring constraints are met during the clone.
That's why the assignments during cloning work exactly like regular property assignments, observing visibility and property hooks. The only tiny difference is that an “outsider” is able to change a `public(set) readonly` property after a `__clone()` method ran to completion and relied on the property in question not changing on the cloned object after it observed its value. This seems not to be something relevant in practice, because why would the exact value of the property only matter during cloning, but not at any other time? Best regards Tim Düsterhus

Rob Landers

1 year ago
On Thu, May 15, 2025, at 17:32, Tim Düsterhus wrote:
> Hi > > Am 2025-05-15 14:14, schrieb Rob Landers: > > For example, if you have a Money type, you'd want to be able to ensure > > it cannot be negative when updating via `with()`. This is super > > important for ensuring constraints are met during the clone. > > That's why the assignments during cloning work exactly like regular > property assignments, observing visibility and property hooks. > > The only tiny difference is that an “outsider” is able to change a > `public(set) readonly` property after a `__clone()` method ran to > completion and relied on the property in question not changing on the > cloned object after it observed its value. This seems not to be > something relevant in practice, because why would the exact value of the > property only matter during cloning, but not at any other time? > > Best regards > Tim Düsterhus >
Hey Tim,
> why would the exact value of the > property only matter during cloning, but not at any other time?
For example, queueing up patches to store/db to commit later; during the clone, it may register various states to ensure the patches are accurate from that point. That's just one example, though, and it suggests calling __clone *before* setting the values is the right answer. I think Larry's idea of just using hooks for validation is also pretty good. As Larry said, the only thing you can really do is throw an exception, and the same would be true in a constructor as well. — Rob

Larry Garfield

1 year ago
On Thu, May 15, 2025, at 2:56 PM, Rob Landers wrote:
> On Thu, May 15, 2025, at 17:32, Tim Düsterhus wrote: >> Hi >> >> Am 2025-05-15 14:14, schrieb Rob Landers: >> > For example, if you have a Money type, you'd want to be able to ensure >> > it cannot be negative when updating via `with()`. This is super >> > important for ensuring constraints are met during the clone. >> >> That's why the assignments during cloning work exactly like regular >> property assignments, observing visibility and property hooks. >> >> The only tiny difference is that an “outsider” is able to change a >> `public(set) readonly` property after a `__clone()` method ran to >> completion and relied on the property in question not changing on the >> cloned object after it observed its value. This seems not to be >> something relevant in practice, because why would the exact value of the >> property only matter during cloning, but not at any other time? >> >> Best regards >> Tim Düsterhus >> > > Hey Tim, > >> why would the exact value of the >> property only matter during cloning, but not at any other time? > > For example, queueing up patches to store/db to commit later; during > the clone, it may register various states to ensure the patches are > accurate from that point. That's just one example, though, and it > suggests calling __clone *before* setting the values is the right > answer. > > I think Larry's idea of just using hooks for validation is also pretty > good. As Larry said, the only thing you can really do is throw an > exception, and the same would be true in a constructor as well. > > — Rob
The limit of hooks is that they're single-property. So depending on how your derived properties are implemented, it may be insufficient. I could easily write such an example (the hooks RFC included some), but how contrived they are, I don't know. --Larry Garfield

Andreas Hennings

1 year ago
On Thu, 15 May 2025 at 13:56, Stephen Reay <php-lists@koalephant.com> wrote:
> > > > > > On 15 May 2025, at 16:44, Andreas Hennings <andreas@dqxtech.net> wrote: > > > > On Thu, 15 May 2025 at 08:24, Stephen Reay <php-lists@koalephant.com> wrote: > > [..] > >> > >> > >> I may be missing something here.. > >> > >> So far the issues are "how do we deal with a parameter for the actual object, vs new properties to apply", "should __clone be called before or after the changes" and "this won't allow regular readonly properties to be modified". > >> > >> Isn't the previous suggestion of passing the new property arguments directly to the __clone method the obvious solution to all three problems? > > > > What exactly should happen then? > > Would the __clone() method be responsible for assigning those properties? > > Or does the __clone() method get the chance to alter the values before > > they are assigned? > > (this would mean they have to be passed by reference) > > I think this last option is the best, because the values in the array > > can be changed without any readonly constraints. > > > > Another option I was thinking of would be to call __clone() after the > > changes are applied, and pass both the original object and the array > > of changes as first parameter. > > But I think this is a dead end. > > > > -- Andreas > > > >> > >> There's no potential for a conflicting property name, the developer can use the new property values in the order they see fit relative to the logic in the __clone call, and it's inherently in scope to write to any (unlocked during __clone) readonly properties. > >> > >> > >> > >> Cheers > >> > >> Stephen > >> > >> > >> > > > > I would suggest that the __clone method should be directly responsible for making any changes, just as it is now when it comes to deep cloning or resetting values. > > Yes I realise it means developers need to then opt in and provide the functionality to support `clone $foo with(bar: "baz")` or whatever syntax is used.
I don't really like this. It would mean that if you add an empty __clone() method, it would prevent all of the automatic setting of values.
> > If the properties are public properties, there's nothing stopping someone writing their own clone_with() in userland now; If someone is using readonly properties I'd suggest they want to specifically manage updates to those properties themselves anyway.
But they already do that in the ->withXyz() methods. A public non-readonly property can be set from anywhere without validation or clean-up, so by default the __clone() method would want to leave it alone. A readonly or non-public property can only be initialized from within the class, so the ->withSomething() method would be the place for cleanup and validation. The default behavior of an empty __clone() method should therefore be to just allow all of the properties being set as they would be without a __clone() method.
> > Additionally it means "clone with" would be usable for non-public properties at the discretion of the developer writing their code.
This is already the case, because that "clone with" for non-public properties can only happen from within methods of the same class (hierarchy). -- Andreas

Stephen Reay

1 year ago
> On 15 May 2025, at 23:49, Andreas Hennings <andreas@dqxtech.net> wrote: > > On Thu, 15 May 2025 at 13:56, Stephen Reay <php-lists@koalephant.com <mailto:php-lists@koalephant.com>> wrote: >> >> >> >> >>> On 15 May 2025, at 16:44, Andreas Hennings <andreas@dqxtech.net> wrote: >>> >>> On Thu, 15 May 2025 at 08:24, Stephen Reay <php-lists@koalephant.com> wrote: >>> [..] >>>> >>>> >>>> I may be missing something here.. >>>> >>>> So far the issues are "how do we deal with a parameter for the actual object, vs new properties to apply", "should __clone be called before or after the changes" and "this won't allow regular readonly properties to be modified". >>>> >>>> Isn't the previous suggestion of passing the new property arguments directly to the __clone method the obvious solution to all three problems? >>> >>> What exactly should happen then? >>> Would the __clone() method be responsible for assigning those properties? >>> Or does the __clone() method get the chance to alter the values before >>> they are assigned? >>> (this would mean they have to be passed by reference) >>> I think this last option is the best, because the values in the array >>> can be changed without any readonly constraints. >>> >>> Another option I was thinking of would be to call __clone() after the >>> changes are applied, and pass both the original object and the array >>> of changes as first parameter. >>> But I think this is a dead end. >>> >>> -- Andreas >>> >>>> >>>> There's no potential for a conflicting property name, the developer can use the new property values in the order they see fit relative to the logic in the __clone call, and it's inherently in scope to write to any (unlocked during __clone) readonly properties. >>>> >>>> >>>> >>>> Cheers >>>> >>>> Stephen >>>> >>>> >>>> >>> >> >> I would suggest that the __clone method should be directly responsible for making any changes, just as it is now when it comes to deep cloning or resetting values. >> >> Yes I realise it means developers need to then opt in and provide the functionality to support `clone $foo with(bar: "baz")` or whatever syntax is used. > > I don't really like this. > It would mean that if you add an empty __clone() method, it would > prevent all of the automatic setting of values. >
To repeat myself: yes. It requires the class developer to opt-in to supporting this feature of the language. Just the same way promoted constructor parameters are opt-in, and don't just create a bunch of properties with the names the caller specified to `new`. An empty or missing clone method would mean it behaves **exactly the way it does now**.
>> >> If the properties are public properties, there's nothing stopping someone writing their own clone_with() in userland now; If someone is using readonly properties I'd suggest they want to specifically manage updates to those properties themselves anyway. > > But they already do that in the ->withXyz() methods. > > A public non-readonly property can be set from anywhere without > validation or clean-up, so by default the __clone() method would want > to leave it alone.
Again, what happens when cloning should be entirely the purview of the developer that writes the class.
> A readonly or non-public property can only be initialized from within > the class, so the ->withSomething() method would be the place for > cleanup and validation. > The default behavior of an empty __clone() method should therefore be > to just allow all of the properties being set as they would be without > a __clone() method. >
I agree that no __clone and an empty __clone should behave the same way. But as I said, I believe they should behave the same way as they do *now*, until the developer opts in to support cloning with new values.

Rowan Tommins [IMSoP]

1 year ago
On 15/05/2025 18:56, Stephen Reay wrote:
> I agree that no __clone and an empty __clone should behave the same > way. But as I said, I believe they should behave the same way as they > do *now*, until the developer opts in to support cloning with new values.
I think what Andreas is saying is that the behaviour of this: $foo = clone($bar, someProperty: 42); Should be consistent with the existing behaviour of this: $foo = clone $bar; $bar->someProperty = 42; An empty or missing __clone method won't prevent `someProperty` being assigned in the existing case, and for everything other than readonly properties, the new syntax is purely sugar for the existing one. If I understand the RFC, the only change in behaviour is for readonly properties, which are "unlocked" during the "clone with" process. That means that if they were previously validated only in the constructor, this syntax can put the object in an unexpected state. However, readonly properties are "protected(set)" by default, so the situations where this can happen are actually quite limited: - Code inside the class itself (private scope) can reasonably be considered to be "opting in" to the feature it's using. - Code in a sub-class (protected scope) can by default over-ride the constructor anyway. - Code outside the class (public scope) will fail unless the property is explicitly "readonly public(set)", which would be pointless if it was always initialised in the constructor. So the only case I can think of where something surprising could happen is: 1. A public or protected readonly property is initialised in a constructor marked "final" 2. A sub-class adds code that uses "clone with" to set that property to a new value The question then is, how worried are we about that scenario?
-- Rowan Tommins [IMSoP]

Volker Dusch

1 year ago
On Fri, May 16, 2025 at 8:46 PM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:
> However, readonly properties are "protected(set)" by default, so the > situations where this can happen are actually quite limited: > > - Code inside the class itself (private scope) can reasonably be > considered to be "opting in" to the feature it's using. > - Code in a sub-class (protected scope) can by default over-ride the > constructor anyway. > - Code outside the class (public scope) will fail unless the property is > explicitly "readonly public(set)", which would be pointless if it was > always initialised in the constructor. > > > So the only case I can think of where something surprising could happen is: > > 1. A public or protected readonly property is initialised in a > constructor marked "final" > 2. A sub-class adds code that uses "clone with" to set that property to > a new value > > The question then is, how worried are we about that scenario? >
Your summary matches my ideas around the topic and I've updated the RFC [1] to better clarify that we consider touching __clone to be out of scope. From our perspective, we are not worried about the Points (1&2) you raised and are not interested in solving this by providing parameters to __clone as the additional cost, issues, and complexity are not a worthwhile tradeoff in our eyes. Kind Regards, Volker [1] https://news-web.php.net/php.internals/127397
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Larry Garfield

1 year ago
On Thu, May 15, 2025, at 1:22 AM, Stephen Reay wrote:
> I may be missing something here.. > > So far the issues are "how do we deal with a parameter for the actual > object, vs new properties to apply", "should __clone be called before > or after the changes" and "this won't allow regular readonly properties > to be modified". > > Isn't the previous suggestion of passing the new property arguments > directly to the __clone method the obvious solution to all three > problems? > > There's no potential for a conflicting property name, the developer can > use the new property values in the order they see fit relative to the > logic in the __clone call, and it's inherently in scope to write to any > (unlocked during __clone) readonly properties.
I did some exploratory design a few years ago on this front, looking at the implications of different possible syntaxes. https://peakd.com/hive-168588/@crell/object-properties-part-2-examples What that article calls "initonly" is essentially what became readonly. The second example is roughly what this RFC would look like if the extra arguments were passed to __clone(). As noted in the article, the result is absolutely awful. Auto-setting the values while using the clone($object, ...$args) syntax is the cleanest solution. Given that experimentation, I would not support an implementation that passes args to __clone and makes the developer figure it out. That just makes a mess. Rob makes a good point elsewhere in thread that running __clone() afterward is a way to allow the object to re-inforce validation if necessary. My concern is whether the method knows it needs to do the extra validation or not, since it may be arbitrarily complex. It would also leave no way to reject the changes other than throwing an exception, though in fairness the same is true of set hooks. Which also begs the question of whether a set hook would be sufficient that __clone() doesn't need to do extra validation? At least in the typical case? One possibility (just brainstorming) would be to update first, then call __clone(), but give clone a new optional arg that just tells it what properties were modified by the clone call. It can then recheck just those properties or ignore it entirely, as it prefers. If that handles only complex cases (eg, firstName was updated so the computed fullName needs to be updated) and set hooks handle the single-property ones, that would probably cover all bases reasonably well. --Larry Garfield

Nicolas Grekas

1 year ago
Le jeu. 15 mai 2025 à 16:06, Larry Garfield <larry@garfieldtech.com> a écrit :
> On Thu, May 15, 2025, at 1:22 AM, Stephen Reay wrote: > > > I may be missing something here.. > > > > So far the issues are "how do we deal with a parameter for the actual > > object, vs new properties to apply", "should __clone be called before > > or after the changes" and "this won't allow regular readonly properties > > to be modified". > > > > Isn't the previous suggestion of passing the new property arguments > > directly to the __clone method the obvious solution to all three > > problems? > > > > There's no potential for a conflicting property name, the developer can > > use the new property values in the order they see fit relative to the > > logic in the __clone call, and it's inherently in scope to write to any > > (unlocked during __clone) readonly properties. > > I did some exploratory design a few years ago on this front, looking at > the implications of different possible syntaxes. > > https://peakd.com/hive-168588/@crell/object-properties-part-2-examples > > What that article calls "initonly" is essentially what became readonly. > The second example is roughly what this RFC would look like if the extra > arguments were passed to __clone(). As noted in the article, the result is > absolutely awful. > > Auto-setting the values while using the clone($object, ...$args) syntax is > the cleanest solution. Given that experimentation, I would not support an > implementation that passes args to __clone and makes the developer figure > it out. That just makes a mess. > > Rob makes a good point elsewhere in thread that running __clone() > afterward is a way to allow the object to re-inforce validation if > necessary. My concern is whether the method knows it needs to do the extra > validation or not, since it may be arbitrarily complex. It would also > leave no way to reject the changes other than throwing an exception, though > in fairness the same is true of set hooks. Which also begs the question of > whether a set hook would be sufficient that __clone() doesn't need to do > extra validation? At least in the typical case? > > One possibility (just brainstorming) would be to update first, then call > __clone(), but give clone a new optional arg that just tells it what > properties were modified by the clone call. It can then recheck just those > properties or ignore it entirely, as it prefers. If that handles only > complex cases (eg, firstName was updated so the computed fullName needs to > be updated) and set hooks handle the single-property ones, that would > probably cover all bases reasonably well.
I like where this is going but here is a variant that'd be even more capable: we could pass the original object to __clone. The benefits I see: - Allow implementing this validation logic you're talking about. - Allow to skip deep-cloning of already updated properties (that's a significant drawback of the current proposal - deep cloning before setting is a potential perf/mem hog built into the engine) : guarding deep-cloning with a strict comparison would be enough. - Allow implementing WeakMap that are able to clone weak-properties as objects are cloned. On this last aspect, I think it's new to the discussion but it's something I've always found very limiting when using weak-map: let's say some metadata are stored about object $foo in a weakmap, it's currently not possible to track those metadata across clones without using some nasty tricks. If __clone were given the original object, it's be easy to duplicate meta-data from $foo to $clone. I have just one concern significant with adding an argument to __clone: it'dbe a BC break to mandate this argument at the declaration level, and adding one right now generates an error with current versions of PHP. However, I think we could (and should if confirmed) provide some FC/BC layer by allowing one to use func_get_args() in __clone. The engine could then verify at compile time that __clone has either one non-nullable "object" argument, or zero. Nicolas

Andreas Hennings

1 year ago
On Fri, 16 May 2025 at 21:59, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> > > > Le jeu. 15 mai 2025 à 16:06, Larry Garfield <larry@garfieldtech.com> a écrit : >> >> On Thu, May 15, 2025, at 1:22 AM, Stephen Reay wrote: >> >> > I may be missing something here.. >> > >> > So far the issues are "how do we deal with a parameter for the actual >> > object, vs new properties to apply", "should __clone be called before >> > or after the changes" and "this won't allow regular readonly properties >> > to be modified". >> > >> > Isn't the previous suggestion of passing the new property arguments >> > directly to the __clone method the obvious solution to all three >> > problems? >> > >> > There's no potential for a conflicting property name, the developer can >> > use the new property values in the order they see fit relative to the >> > logic in the __clone call, and it's inherently in scope to write to any >> > (unlocked during __clone) readonly properties. >> >> I did some exploratory design a few years ago on this front, looking at the implications of different possible syntaxes. >> >> https://peakd.com/hive-168588/@crell/object-properties-part-2-examples >> >> What that article calls "initonly" is essentially what became readonly. The second example is roughly what this RFC would look like if the extra arguments were passed to __clone(). As noted in the article, the result is absolutely awful. >> >> Auto-setting the values while using the clone($object, ...$args) syntax is the cleanest solution. Given that experimentation, I would not support an implementation that passes args to __clone and makes the developer figure it out. That just makes a mess. >> >> Rob makes a good point elsewhere in thread that running __clone() afterward is a way to allow the object to re-inforce validation if necessary. My concern is whether the method knows it needs to do the extra validation or not, since it may be arbitrarily complex. It would also leave no way to reject the changes other than throwing an exception, though in fairness the same is true of set hooks. Which also begs the question of whether a set hook would be sufficient that __clone() doesn't need to do extra validation? At least in the typical case? >> >> One possibility (just brainstorming) would be to update first, then call __clone(), but give clone a new optional arg that just tells it what properties were modified by the clone call. It can then recheck just those properties or ignore it entirely, as it prefers. If that handles only complex cases (eg, firstName was updated so the computed fullName needs to be updated) and set hooks handle the single-property ones, that would probably cover all bases reasonably well. > > > I like where this is going but here is a variant that'd be even more capable: > > we could pass the original object to __clone.
My proposal earlier was to pass the original object _and_ the values that were passed to the clone call, by reference. And this would happen before those values are assigned to the object. class MyClass { public function __construct( public readonly int $x, public readonly int $y, public readonly int $z, ) {} public function __clone(object $original, array &$values): void { // Set a value directly, and modify it. if (isset($values['x'])) { $this->x = $values['x'] * 10; // Prevent that the same property is assigned again. unset($values['x']); } } } $obj = new C(5, 7, 9); $clone = clone($obj, x: 2, y: 3); assert($clone->x === 20); // x was update in __clone(). assert($clone->y === 3); // y was auto-updated after __clone(). assert($clone->z === 9); // z was not touched at all.
> > The benefits I see: > - Allow implementing this validation logic you're talking about. > - Allow to skip deep-cloning of already updated properties (that's a significant drawback of the current proposal - deep cloning before setting is a potential perf/mem hog built into the engine) : guarding deep-cloning with a strict comparison would be enough. > - Allow implementing WeakMap that are able to clone weak-properties as objects are cloned. > > On this last aspect, I think it's new to the discussion but it's something I've always found very limiting when using weak-map: let's say some metadata are stored about object $foo in a weakmap, it's currently not possible to track those metadata across clones without using some nasty tricks. If __clone were given the original object, it's be easy to duplicate meta-data from $foo to $clone. > > I have just one concern significant with adding an argument to __clone: it'dbe a BC break to mandate this argument at the declaration level, and adding one right now generates an error with current versions of PHP. > However, I think we could (and should if confirmed) provide some FC/BC layer by allowing one to use func_get_args() in __clone. The engine could then verify at compile time that __clone has either one non-nullable "object" argument, or zero.
This seems reasonable.

Nicolas Grekas

1 year ago
Le lun. 19 mai 2025 à 16:30, Andreas Hennings <andreas@dqxtech.net> a écrit :
> On Fri, 16 May 2025 at 21:59, Nicolas Grekas > <nicolas.grekas+php@gmail.com> wrote: > > > > > > > > Le jeu. 15 mai 2025 à 16:06, Larry Garfield <larry@garfieldtech.com> a > écrit : > >> > >> On Thu, May 15, 2025, at 1:22 AM, Stephen Reay wrote: > >> > >> > I may be missing something here.. > >> > > >> > So far the issues are "how do we deal with a parameter for the actual > >> > object, vs new properties to apply", "should __clone be called before > >> > or after the changes" and "this won't allow regular readonly > properties > >> > to be modified". > >> > > >> > Isn't the previous suggestion of passing the new property arguments > >> > directly to the __clone method the obvious solution to all three > >> > problems? > >> > > >> > There's no potential for a conflicting property name, the developer > can > >> > use the new property values in the order they see fit relative to the > >> > logic in the __clone call, and it's inherently in scope to write to > any > >> > (unlocked during __clone) readonly properties. > >> > >> I did some exploratory design a few years ago on this front, looking at > the implications of different possible syntaxes. > >> > >> https://peakd.com/hive-168588/@crell/object-properties-part-2-examples > >> > >> What that article calls "initonly" is essentially what became > readonly. The second example is roughly what this RFC would look like if > the extra arguments were passed to __clone(). As noted in the article, the > result is absolutely awful. > >> > >> Auto-setting the values while using the clone($object, ...$args) syntax > is the cleanest solution. Given that experimentation, I would not support > an implementation that passes args to __clone and makes the developer > figure it out. That just makes a mess. > >> > >> Rob makes a good point elsewhere in thread that running __clone() > afterward is a way to allow the object to re-inforce validation if > necessary. My concern is whether the method knows it needs to do the extra > validation or not, since it may be arbitrarily complex. It would also > leave no way to reject the changes other than throwing an exception, though > in fairness the same is true of set hooks. Which also begs the question of > whether a set hook would be sufficient that __clone() doesn't need to do > extra validation? At least in the typical case? > >> > >> One possibility (just brainstorming) would be to update first, then > call __clone(), but give clone a new optional arg that just tells it what > properties were modified by the clone call. It can then recheck just those > properties or ignore it entirely, as it prefers. If that handles only > complex cases (eg, firstName was updated so the computed fullName needs to > be updated) and set hooks handle the single-property ones, that would > probably cover all bases reasonably well. > > > > > > I like where this is going but here is a variant that'd be even more > capable: > > > > we could pass the original object to __clone. > > My proposal earlier was to pass the original object _and_ the values > that were passed to the clone call, by reference. >
And this would happen before those values are assigned to the object.
> > class MyClass { > public function __construct( > public readonly int $x, > public readonly int $y, > public readonly int $z, > ) {} > public function __clone(object $original, array &$values): void { > // Set a value directly, and modify it. > if (isset($values['x'])) { > $this->x = $values['x'] * 10; > // Prevent that the same property is assigned again. > unset($values['x']); > } > } > } > > $obj = new C(5, 7, 9); > $clone = clone($obj, x: 2, y: 3); > assert($clone->x === 20); // x was update in __clone(). > assert($clone->y === 3); // y was auto-updated after __clone(). > assert($clone->z === 9); // z was not touched at all. >
I'm not sure I understand, there might be missing bits to your idea, eg where is visibility enforced? why is pass-by-ref needed at all? Pass-by-ref makes me think this is a bad idea already :) Also, WDYT of my simpler proposal itself? Wouldn't it cover all use cases? Note that I don't see the need for operations like in your example (transforming a value while cloning). This looks like the job of a setter or a hook instead, not a cloner.
> > > > The benefits I see: > > - Allow implementing this validation logic you're talking about. > > - Allow to skip deep-cloning of already updated properties (that's a > significant drawback of the current proposal - deep cloning before setting > is a potential perf/mem hog built into the engine) : guarding deep-cloning > with a strict comparison would be enough. > > - Allow implementing WeakMap that are able to clone weak-properties as > objects are cloned. > > > > On this last aspect, I think it's new to the discussion but it's > something I've always found very limiting when using weak-map: let's say > some metadata are stored about object $foo in a weakmap, it's currently not > possible to track those metadata across clones without using some nasty > tricks. If __clone were given the original object, it's be easy to > duplicate meta-data from $foo to $clone. > > > > I have just one concern significant with adding an argument to __clone: > it'dbe a BC break to mandate this argument at the declaration level, and > adding one right now generates an error with current versions of PHP. > > However, I think we could (and should if confirmed) provide some FC/BC > layer by allowing one to use func_get_args() in __clone. The engine could > then verify at compile time that __clone has either one non-nullable > "object" argument, or zero. > > This seems reasonable. > >
I'd be happy to know what Volker and Tim think about this? I read they excluded any change to __clone in the RFC, but I think it should still be possible to discuss this, especially if it provides the path to the desired solution. Nicolas

Andreas Hennings

1 year ago
On Mon, 19 May 2025 at 17:13, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> > > > Le lun. 19 mai 2025 à 16:30, Andreas Hennings <andreas@dqxtech.net> a écrit : >> >> On Fri, 16 May 2025 at 21:59, Nicolas Grekas >> <nicolas.grekas+php@gmail.com> wrote: >> > >> > >> > >> > Le jeu. 15 mai 2025 à 16:06, Larry Garfield <larry@garfieldtech.com> a écrit : >> >> >> >> On Thu, May 15, 2025, at 1:22 AM, Stephen Reay wrote: >> >> >> >> > I may be missing something here.. >> >> > >> >> > So far the issues are "how do we deal with a parameter for the actual >> >> > object, vs new properties to apply", "should __clone be called before >> >> > or after the changes" and "this won't allow regular readonly properties >> >> > to be modified". >> >> > >> >> > Isn't the previous suggestion of passing the new property arguments >> >> > directly to the __clone method the obvious solution to all three >> >> > problems? >> >> > >> >> > There's no potential for a conflicting property name, the developer can >> >> > use the new property values in the order they see fit relative to the >> >> > logic in the __clone call, and it's inherently in scope to write to any >> >> > (unlocked during __clone) readonly properties. >> >> >> >> I did some exploratory design a few years ago on this front, looking at the implications of different possible syntaxes. >> >> >> >> https://peakd.com/hive-168588/@crell/object-properties-part-2-examples >> >> >> >> What that article calls "initonly" is essentially what became readonly. The second example is roughly what this RFC would look like if the extra arguments were passed to __clone(). As noted in the article, the result is absolutely awful. >> >> >> >> Auto-setting the values while using the clone($object, ...$args) syntax is the cleanest solution. Given that experimentation, I would not support an implementation that passes args to __clone and makes the developer figure it out. That just makes a mess. >> >> >> >> Rob makes a good point elsewhere in thread that running __clone() afterward is a way to allow the object to re-inforce validation if necessary. My concern is whether the method knows it needs to do the extra validation or not, since it may be arbitrarily complex. It would also leave no way to reject the changes other than throwing an exception, though in fairness the same is true of set hooks. Which also begs the question of whether a set hook would be sufficient that __clone() doesn't need to do extra validation? At least in the typical case? >> >> >> >> One possibility (just brainstorming) would be to update first, then call __clone(), but give clone a new optional arg that just tells it what properties were modified by the clone call. It can then recheck just those properties or ignore it entirely, as it prefers. If that handles only complex cases (eg, firstName was updated so the computed fullName needs to be updated) and set hooks handle the single-property ones, that would probably cover all bases reasonably well. >> > >> > >> > I like where this is going but here is a variant that'd be even more capable: >> > >> > we could pass the original object to __clone. >> >> My proposal earlier was to pass the original object _and_ the values >> that were passed to the clone call, by reference. >> >> And this would happen before those values are assigned to the object. >> >> class MyClass { >> public function __construct( >> public readonly int $x, >> public readonly int $y, >> public readonly int $z, >> ) {} >> public function __clone(object $original, array &$values): void { >> // Set a value directly, and modify it. >> if (isset($values['x'])) { >> $this->x = $values['x'] * 10; >> // Prevent that the same property is assigned again. >> unset($values['x']); >> } >> } >> } >> >> $obj = new C(5, 7, 9); >> $clone = clone($obj, x: 2, y: 3); >> assert($clone->x === 20); // x was update in __clone(). >> assert($clone->y === 3); // y was auto-updated after __clone(). >> assert($clone->z === 9); // z was not touched at all. > > > I'm not sure I understand, there might be missing bits to your idea, eg where is visibility enforced? why is pass-by-ref needed at all? > Pass-by-ref makes me think this is a bad idea already :)
Maybe we are looking at different problems to be solved. To me, the main questions are: - Could a __clone() method want to behave differently depending which property values are passed with the clone call? - Can there be conflicts between operations we would normally do in __clone() and values passed to __clone()? - Should we prevent or allow double-write to a readonly property? That is, if one write happens in __clone(), and the other write happens automatically due to the property value passed to clone(..). And to clarify my proposal: - Everything is the same as in the RFC (except points below) - Same as in the RFC, the __clone() method is called _after_ the original object values have been copied over, but _before_ any of the property values passed as arguments to clone($obj, ...$values) are assigned. - Same as in the RFC, the values passed to clone($obj, ...$values) are assigned automatically after the __clone() method. - Unlike the RFC, the __clone() method can see (and validate) the values that were passed to __clone($obj, ...$values) - Unlike the RFC, the __clone() method can _alter_ the values passed to __clone($obj, ...$values) before they are assigned. - As in the RFC, readonly properties can be written only once on clone. The __clone() method can prevent a double write by unsetting that key in $values. Consequence: By leaving the __clone() method empty, all values are assigned automatically, as in the RFC.
> where is visibility enforced?
Exactly as in the RFC. When clone($obj, ...$values) is called, and before __clone() is invoked, php has to verify which of the properties are legal to be updated in this way, based on property visibility and readonly status, and depending on the scope from which it is called. Actually this raises some questions that I did not think of before: - After __clone() is invoked, does php need to validate again? - What happens to private properties that are not accessible from the scope of the __clone() method? Are they also passed in the $values array?
> > Also, WDYT of my simpler proposal itself? Wouldn't it cover all use cases?
First, I want to make sure I understand correctly: - Unlike the RFC, we want to call __clone() _after_ the values from clone($obj, ...$values) are assigned. (I assume this because otherwise the two objects would be identical, and the original object would be useless) - Unlike the RFC, we pass the original object as a parameter to __clone(). Tbh I am not sure if the use cases I think of are relevant or not :) I mostly think of it in terms of functional completeness, without trying to speculate why a developer would want to do this or that during __clone(). Let's look at the "benefits" section from your earlier mail.
> The benefits I see: > - Allow implementing this validation logic you're talking about.
Having __clone() called after the values are assigned, as you propose, makes it possible to run an integrity check on the object itself. On the other hand, this may leave us with a short moment of possibly "bad" property values. Having __clone() called before the values are assigned means we have to validate the values array, not the object.
> - Allow to skip deep-cloning of already updated properties (that's a significant drawback of the current proposal - deep cloning before setting is a potential perf/mem hog built into the engine) : guarding deep-cloning with a strict comparison would be enough.
With __clone() called after the values are assigned, and with access to the original object, we can check $this->prop === $old->prop to see whether a specific property was updated (and therefore should not be deep cloned). With __clone() called before the values are assigned, and with access to the values array, we would check isset($values['prop']) instead. (or really array_key_exists()) In general this would produce the same result, unless a property was assigned the same value that it had before. Another question is about "dependent properties", e.g. for lazily filled calculated values. With access to the old object we would have to check $this->prop !== $old->prop to then see which dependent properties need to be reset or recalculated. With access to the values we would check isset($values['prop']) instead.
> - Allow implementing WeakMap that are able to clone weak-properties as objects are cloned.
I don't feel informed or qualified to talk about this one.. So, with __clone() called after and with the original object, we compare old and new when looking for a specific property. With access to an array of updated (or to be updated) properties, we could iterate over the changes, or we could check whether the changelist is empty, which is less obvious to do by comparing old and new instance.
> > Note that I don't see the need for operations like in your example (transforming a value while cloning). > This looks like the job of a setter or a hook instead, not a cloner.
Tbh, most of the classes I wrote that would benefit from a "clone with" did not really need a __clone() method, because everything in there was already immutable. To me, the scenarios where we want both are quite speculative, so this is why my examples might not be the most realistic.

Nicolas Grekas

1 year ago
Le lun. 19 mai 2025 à 19:06, Andreas Hennings <andreas@dqxtech.net> a écrit :
> On Mon, 19 May 2025 at 17:13, Nicolas Grekas > <nicolas.grekas+php@gmail.com> wrote: > > > > > > > > Le lun. 19 mai 2025 à 16:30, Andreas Hennings <andreas@dqxtech.net> a > écrit : > >> > >> On Fri, 16 May 2025 at 21:59, Nicolas Grekas > >> <nicolas.grekas+php@gmail.com> wrote: > >> > > >> > > >> > > >> > Le jeu. 15 mai 2025 à 16:06, Larry Garfield <larry@garfieldtech.com> > a écrit : > >> >> > >> >> On Thu, May 15, 2025, at 1:22 AM, Stephen Reay wrote: > >> >> > >> >> > I may be missing something here.. > >> >> > > >> >> > So far the issues are "how do we deal with a parameter for the > actual > >> >> > object, vs new properties to apply", "should __clone be called > before > >> >> > or after the changes" and "this won't allow regular readonly > properties > >> >> > to be modified". > >> >> > > >> >> > Isn't the previous suggestion of passing the new property arguments > >> >> > directly to the __clone method the obvious solution to all three > >> >> > problems? > >> >> > > >> >> > There's no potential for a conflicting property name, the > developer can > >> >> > use the new property values in the order they see fit relative to > the > >> >> > logic in the __clone call, and it's inherently in scope to write > to any > >> >> > (unlocked during __clone) readonly properties. > >> >> > >> >> I did some exploratory design a few years ago on this front, looking > at the implications of different possible syntaxes. > >> >> > >> >> > https://peakd.com/hive-168588/@crell/object-properties-part-2-examples > >> >> > >> >> What that article calls "initonly" is essentially what became > readonly. The second example is roughly what this RFC would look like if > the extra arguments were passed to __clone(). As noted in the article, the > result is absolutely awful. > >> >> > >> >> Auto-setting the values while using the clone($object, ...$args) > syntax is the cleanest solution. Given that experimentation, I would not > support an implementation that passes args to __clone and makes the > developer figure it out. That just makes a mess. > >> >> > >> >> Rob makes a good point elsewhere in thread that running __clone() > afterward is a way to allow the object to re-inforce validation if > necessary. My concern is whether the method knows it needs to do the extra > validation or not, since it may be arbitrarily complex. It would also > leave no way to reject the changes other than throwing an exception, though > in fairness the same is true of set hooks. Which also begs the question of > whether a set hook would be sufficient that __clone() doesn't need to do > extra validation? At least in the typical case? > >> >> > >> >> One possibility (just brainstorming) would be to update first, then > call __clone(), but give clone a new optional arg that just tells it what > properties were modified by the clone call. It can then recheck just those > properties or ignore it entirely, as it prefers. If that handles only > complex cases (eg, firstName was updated so the computed fullName needs to > be updated) and set hooks handle the single-property ones, that would > probably cover all bases reasonably well. > >> > > >> > > >> > I like where this is going but here is a variant that'd be even more > capable: > >> > > >> > we could pass the original object to __clone. > >> > >> My proposal earlier was to pass the original object _and_ the values > >> that were passed to the clone call, by reference. > >> > >> And this would happen before those values are assigned to the object. > >> > >> class MyClass { > >> public function __construct( > >> public readonly int $x, > >> public readonly int $y, > >> public readonly int $z, > >> ) {} > >> public function __clone(object $original, array &$values): void { > >> // Set a value directly, and modify it. > >> if (isset($values['x'])) { > >> $this->x = $values['x'] * 10; > >> // Prevent that the same property is assigned again. > >> unset($values['x']); > >> } > >> } > >> } > >> > >> $obj = new C(5, 7, 9); > >> $clone = clone($obj, x: 2, y: 3); > >> assert($clone->x === 20); // x was update in __clone(). > >> assert($clone->y === 3); // y was auto-updated after __clone(). > >> assert($clone->z === 9); // z was not touched at all. > > > > > > I'm not sure I understand, there might be missing bits to your idea, eg > where is visibility enforced? why is pass-by-ref needed at all? > > Pass-by-ref makes me think this is a bad idea already :) > > Maybe we are looking at different problems to be solved. > To me, the main questions are: > - Could a __clone() method want to behave differently depending which > property values are passed with the clone call? >
Definitely yes, at least to skip triggering a costly deep cloning operation. - Can there be conflicts between operations we would normally do in
> __clone() and values passed to __clone()? >
I don't see any. BTW, I had a look at what is done within __clone methods in Symfony, and all implementations fall down into 4 categories: - incrementing some counter for tracking management purposes - resetting the state of the clone (at least for some transient properties) - deep-cloning - forbidding clone at all (throwing or making the method private)
> - Should we prevent or allow double-write to a readonly property? That > is, if one write happens in __clone(), and the other write happens > automatically due to the property value passed to clone(..). >
This question doesn't really make sense IMHO. What matters is readonly semantics, which must be preserved. The fact that there are two or more steps to achieve the target state doesn't matter.
> And to clarify my proposal: > - Everything is the same as in the RFC (except points below) > - Same as in the RFC, the __clone() method is called _after_ the > original object values have been copied over, but _before_ any of the > property values passed as arguments to clone($obj, ...$values) are > assigned. > - Same as in the RFC, the values passed to clone($obj, ...$values) are > assigned automatically after the __clone() method. > - Unlike the RFC, the __clone() method can see (and validate) the > values that were passed to __clone($obj, ...$values) > - Unlike the RFC, the __clone() method can _alter_ the values passed > to __clone($obj, ...$values) before they are assigned. > - As in the RFC, readonly properties can be written only once on > clone. The __clone() method can prevent a double write by unsetting > that key in $values. >
Thanks for the clarification. About this last item: the RFC has been updated on this topic. Also preventing double-writes by unsetting a by-ref array looks terrible, no chance this can be the best API, sorry :)
> Consequence: > By leaving the __clone() method empty, all values are assigned > automatically, as in the RFC. > > > where is visibility enforced? > > Exactly as in the RFC. > When clone($obj, ...$values) is called, and before __clone() is > invoked, php has to verify which of the properties are legal to be > updated in this way, based on property visibility and readonly status, > and depending on the scope from which it is called. > > Actually this raises some questions that I did not think of before: > - After __clone() is invoked, does php need to validate again? > - What happens to private properties that are not accessible from the > scope of the __clone() method? Are they also passed in the $values > array? >
That's definitely an issue with any approach that relies on passing property names. This doesn't happen of course if we pass only the original object and rely on === to know what changed.
> > > Also, WDYT of my simpler proposal itself? Wouldn't it cover all use > cases? > > First, I want to make sure I understand correctly: > - Unlike the RFC, we want to call __clone() _after_ the values from > clone($obj, ...$values) are assigned. > (I assume this because otherwise the two objects would be identical, > and the original object would be useless) > - Unlike the RFC, we pass the original object as a parameter to __clone(). >
You've got it right, yes.
> > Tbh I am not sure if the use cases I think of are relevant or not :) > I mostly think of it in terms of functional completeness, without > trying to speculate why a developer would want to do this or that > during __clone(). > > Let's look at the "benefits" section from your earlier mail. > > > The benefits I see: > > - Allow implementing this validation logic you're talking about. > > Having __clone() called after the values are assigned, as you propose, > makes it possible to run an integrity check on the object itself. On > the other hand, this may leave us with a short moment of possibly > "bad" property values. >
Having __clone() called before the values are assigned means we have
> to validate the values array, not the object. >
This concern matters only if that temporary state is observable from the outside. Which isn't going to be the case.
> > - Allow to skip deep-cloning of already updated properties (that's a > significant drawback of the current proposal - deep cloning before setting > is a potential perf/mem hog built into the engine) : guarding deep-cloning > with a strict comparison would be enough. > > With __clone() called after the values are assigned, and with access > to the original object, we can check $this->prop === $old->prop to see > whether a specific property was updated (and therefore should not be > deep cloned). > With __clone() called before the values are assigned, and with access > to the values array, we would check isset($values['prop']) instead. > (or really array_key_exists()) > In general this would produce the same result, unless a property was > assigned the same value that it had before. >
Another question is about "dependent properties", e.g. for lazily
> filled calculated values. > With access to the old object we would have to check $this->prop !== > $old->prop to then see which dependent properties need to be reset or > recalculated. > With access to the values we would check isset($values['prop']) instead. > > > - Allow implementing WeakMap that are able to clone weak-properties as > objects are cloned. > > I don't feel informed or qualified to talk about this one.. > > So, with __clone() called after and with the original object, we > compare old and new when looking for a specific property. > With access to an array of updated (or to be updated) properties, we > could iterate over the changes, or we could check whether the > changelist is empty, which is less obvious to do by comparing old and > new instance. >
I think the above concern with private properties and the strange by-ref API both rule out the approach. The one I propose actually works and is simpler.
> Note that I don't see the need for operations like in your example > (transforming a value while cloning). > > This looks like the job of a setter or a hook instead, not a cloner. > > Tbh, most of the classes I wrote that would benefit from a "clone > with" did not really need a __clone() method, because everything in > there was already immutable. > To me, the scenarios where we want both are quite speculative, so this > is why my examples might not be the most realistic. >
Think deep-cloning. This does require __clone + has to support clone-with.
>> > The benefits I see: > >> > - Allow implementing this validation logic you're talking about. > >> > - Allow to skip deep-cloning of already updated properties (that's a > significant drawback of the current proposal - deep cloning before setting > is a potential perf/mem hog built into the engine) : guarding deep-cloning > with a strict comparison would be enough. > >> > - Allow implementing WeakMap that are able to clone weak-properties > as objects are cloned. > >> > > >> > On this last aspect, I think it's new to the discussion but it's > something I've always found very limiting when using weak-map: let's say > some metadata are stored about object $foo in a weakmap, it's currently not > possible to track those metadata across clones without using some nasty > tricks. If __clone were given the original object, it's be easy to > duplicate meta-data from $foo to $clone. > >> > > >> > I have just one concern significant with adding an argument to > __clone: it'dbe a BC break to mandate this argument at the declaration > level, and adding one right now generates an error with current versions of > PHP. > >> > However, I think we could (and should if confirmed) provide some > FC/BC layer by allowing one to use func_get_args() in __clone. The engine > could then verify at compile time that __clone has either one non-nullable > "object" argument, or zero. > >> > >> This seems reasonable. > >> > > > > I'd be happy to know what Volker and Tim think about this? I read they > excluded any change to __clone in the RFC, but I think it should still be > possible to discuss this, especially if it provides the path to the desired > solution. >
Nicolas

Volker Dusch

1 year ago
Hey everyone, Thank you for the participation so far, since the start of the discussion, from feedback on and off list, I've added a couple of examples: - https://wiki.php.net/rfc/clone_with_v2#:~:text=dynamic%20property%20creation%20follows%20established%20PHP%20rules - https://wiki.php.net/rfc/clone_with_v2#:~:text=Positional%20parameters - We removed the behavior of __clone locking readonly properties when the function updates them for forward compatibility concerns raised in https://externals.io/message/127353#127390 - Updated https://wiki.php.net/rfc/clone_with_v2#design_goals mentioning there that consider touching __clone or adding parameters a non-goal due to complexity and BC implications. - Updated the property name of the varadic parameter from $updatedProperties to $withProperties We're still looking for feedback on the ...variadic approach to the Syntax: https://wiki.php.net/rfc/clone_with_v2#open_issues, as we only got one reply so far on the topic. Also thanks to theodorejb for touching up phrasing, correcting spelling mistakes and so on: https://wiki.php.net/rfc/clone_with_v2?do=revisions Kind Regards, Volker
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Larry Garfield

1 year ago
On Mon, May 19, 2025, at 5:48 AM, Volker Dusch wrote:
> Hey everyone, > > Thank you for the participation so far, since the start of the > discussion, from feedback on and off list, I've added a couple of > examples: > > - > https://wiki.php.net/rfc/clone_with_v2#:~:text=dynamic%20property%20creation%20follows%20established%20PHP%20rules > - https://wiki.php.net/rfc/clone_with_v2#:~:text=Positional%20parameters > - We removed the behavior of __clone locking readonly properties when > the function updates them for forward compatibility concerns raised in > https://externals.io/message/127353#127390 > - Updated https://wiki.php.net/rfc/clone_with_v2#design_goals > mentioning there that consider touching __clone or adding parameters a > non-goal due to complexity and BC implications. > - Updated the property name of the varadic parameter from > $updatedProperties to $withProperties > > We're still looking for feedback on the ...variadic approach to the > Syntax: https://wiki.php.net/rfc/clone_with_v2#open_issues, as we only > got one reply so far on the topic. > > Also thanks to theodorejb for touching up phrasing, correcting spelling > mistakes and so on: https://wiki.php.net/rfc/clone_with_v2?do=revisions > > Kind Regards, > Volker
For positional parameters, I don't see any way that they'd work or do what someone expects. So why not just block them entirely instead of relying on dynamic properties to warn-but-sorta-work? --Larry Garfield

Tim Düsterhus

1 year ago
Hi Am 2025-05-19 15:30, schrieb Larry Garfield:
> For positional parameters, I don't see any way that they'd work or do > what someone expects. So why not just block them entirely instead of > relying on dynamic properties to warn-but-sorta-work?
For better or worse PHP supports numeric properties in objects and it does not seem correct to make this an artificial limitation when the behavior of numeric array keys follows the existing semantics (e.g. when casting an array to an object). Best regards Tim Düsterhus

Tim Düsterhus

1 year ago
Hi Am 2025-05-19 12:48, schrieb Volker Dusch:
> We're still looking for feedback on the ...variadic approach to the > Syntax: > https://wiki.php.net/rfc/clone_with_v2#open_issues, as we only got one > reply so far on the topic.
I was hoping for some additional opinions here before adding my own, but since this does not appear to happen, adding my personal opinion on this matter now: *Some* property name being completely incompatible with “clone with” (no matter how the first parameter is going to be called) is a limitation that should not exist, it feels like a feature that is broken by design and I think I really would hate it if the documentation of this RFC would need a “Caution: It is not possible to reassign a property called '$object', due to a parameter name conflict”. Adjusting the signature to `clone(object $object, array $withProperties)` would not have this problem and I don't consider the additional verbosity of an array literal to be a problem. Static analysis tools already understand array shapes and would need adjustments either way to understand the semantics. From an implementation PoV a regular array parameter would also be simpler, since the implementation would be able to simply pass along the input array, whereas the “variadic” syntax needs special handling to combine positional parameters with named parameters into a single array that is then used in the cloning process. Syntax-wise there might also be a middle-ground. Similarly to how this RFC turns `clone()` into a function, the `array()` syntax also looks like a function call and would naturally extend to named parameters. While mixing positional and named parameters would probably get complex, allowing purely named parameters would trivially be possible (without any function call overhead). It would also allow using the first-class-callable syntax with `array(...)`, something I would liked to have in the past. A proof of concept PR is at: https://github.com/php/php-src/pull/18613 Combining named-parameter `array()` syntax with clone taking a array as the second parameter would allow for the following, which might combine the best of both worlds? clone($obj, array(foo: 1, bar: "baz", object: "this is not blocked")); Best regards Tim Düsterhus

Nicolas Grekas

1 year ago
Hi Tim, Le mer. 21 mai 2025 à 16:15, Tim Düsterhus <tim@bastelstu.be> a écrit :
> Hi > > Am 2025-05-19 12:48, schrieb Volker Dusch: > > We're still looking for feedback on the ...variadic approach to the > > Syntax: > > https://wiki.php.net/rfc/clone_with_v2#open_issues, as we only got one > > reply so far on the topic. > > I was hoping for some additional opinions here before adding my own, but > since this does not appear to happen, adding my personal opinion on this > matter now: > > *Some* property name being completely incompatible with “clone with” (no > matter how the first parameter is going to be called) is a limitation > that should not exist, it feels like a feature that is broken by design > and I think I really would hate it if the documentation of this RFC > would need a “Caution: It is not possible to reassign a property called > '$object', due to a parameter name conflict”. > > Adjusting the signature to `clone(object $object, array > $withProperties)` would not have this problem and I don't consider the > additional verbosity of an array literal to be a problem. Static > analysis tools already understand array shapes and would need > adjustments either way to understand the semantics. > > From an implementation PoV a regular array parameter would also be > simpler, since the implementation would be able to simply pass along the > input array, whereas the “variadic” syntax needs special handling to > combine positional parameters with named parameters into a single array > that is then used in the cloning process. > > Syntax-wise there might also be a middle-ground. Similarly to how this > RFC turns `clone()` into a function, the `array()` syntax also looks > like a function call and would naturally extend to named parameters. > While mixing positional and named parameters would probably get complex, > allowing purely named parameters would trivially be possible (without > any function call overhead). It would also allow using the > first-class-callable syntax with `array(...)`, something I would liked > to have in the past. A proof of concept PR is at: > > https://github.com/php/php-src/pull/18613 > > Combining named-parameter `array()` syntax with clone taking a array as > the second parameter would allow for the following, which might combine > the best of both worlds? > > clone($obj, array(foo: 1, bar: "baz", object: "this is not > blocked")); > >
Thanks for sharing your insights. This looks a bit far reaching for the RFC. On my side, my opinion is: don't make clone a function call. I've never missed not being able to call clone as a callback. It's trivial to write a short function using the operator when in need. Nicolas

Tim Düsterhus

1 year ago
Hi Am 2025-05-21 16:27, schrieb Nicolas Grekas:
> Thanks for sharing your insights. This looks a bit far reaching for the > RFC.
Making `array()` a function / allowing named parameter syntax with `array()` would be a separate RFC.
> On my side, my opinion is: don't make clone a function call. I've never > missed not being able to call clone as a callback. It's trivial to > write a > short function using the operator when in need.
It's an intentional design goal of this RFC to borrow the function call syntax to avoid inventing something that does not yet exist in PHP and to avoid blocking additional keywords (“with”). Making `clone()` an actual function greatly simplifies the implementation, since all the heavy lifting around parameter parsing is already provided by the engine [1] and it also ensures that the behavior is consistent with the behavior implied by the used syntax. If the second parameter would be a regular array rather than using the named parameter syntax, making clone() a function would not be necessary (but wouldn't make things harder either). Best regards Tim Düsterhus [1] As an example, with the currently proposed named parameter syntax, unpacking arbitrary Traversables with `clone($obj, ...$traversable)` would need to be reimplemented specifically for `clone()`.

Theodore Brown

1 year ago
On Wed, May 21, 2025 at 09:13 Tim Düsterhus wrote: > Am 2025-05-19 12:48, schrieb Volker Dusch: >> We're still looking for feedback on the ...variadic approach to the >> Syntax: >> https://wiki.php.net/rfc/clone_with_v2#open_issues, as we only got one >> reply so far on the topic. > > ... > > *Some* property name being completely incompatible with “clone with” (no > matter how the first parameter is going to be called) is a limitation > that should not exist, it feels like a feature that is broken by design > and I think I really would hate it if the documentation of this RFC > would need a “Caution: It is not possible to reassign a property called > '$object', due to a parameter name conflict”. > > Adjusting the signature to `clone(object $object, array $withProperties)` > would not have this problem and I don't consider the additional verbosity > of an array literal to be a problem. Static analysis tools already > understand array shapes and would need adjustments either way to > understand the semantics. > > From an implementation PoV a regular array parameter would also be > simpler, since the implementation would be able to simply pass along the > input array, whereas the “variadic” syntax needs special handling to > combine positional parameters with named parameters into a single array > that is then used in the cloning process. The more I've thought about it, the more I also don't like the variadic approach. It seems like a hack to try to get `property: value` syntax for "free", but at a fundamental level the API doesn't make sense for its purpose. Not only does it prevent setting a property with the name of the first parameter (with no workaround), but it also means that someone could call the function with positional rather than named arguments, which as the RFC admits "for clone this is usually not useful". Even if it's slightly less ergonomic to quote property names in array keys, I agree with Tim that it would be better to change the function signature to: function clone(object $object, array $withProperties): object {} This simply removes the issue of not being able to set a certain property name, as well as the confusing positional parameter behavior. > Syntax-wise there might also be a middle-ground. Similarly to how this > RFC turns `clone()` into a function, the `array()` syntax also looks > like a function call and would naturally extend to named parameters. > While mixing positional and named parameters would probably get complex, > allowing purely named parameters would trivially be possible (without > any function call overhead). It would also allow using the > first-class-callable syntax with `array(...)`, something I would liked > to have in the past. A proof of concept PR is at: > > https://github.com/php/php-src/pull/18613 > > Combining named-parameter `array()` syntax with clone taking a array as > the second parameter would allow for the following, which might combine > the best of both worlds? > > clone($obj, array(foo: 1, bar: "baz", object: "this is not blocked")); I really like this idea! If it can work without any function call overhead, it would enable more ergonomic array creation not only for `clone()` but also in many other common scenarios. Regards, Theodore

Tim Düsterhus

1 year ago
Hi Am 2025-05-21 17:27, schrieb Theodore Brown:
>> Combining named-parameter `array()` syntax with clone taking a array >> as >> the second parameter would allow for the following, which might >> combine >> the best of both worlds? >> >> clone($obj, array(foo: 1, bar: "baz", object: "this is not >> blocked")); > > I really like this idea! If it can work without any function call > overhead, > it would enable more ergonomic array creation not only for `clone()` > but > also in many other common scenarios.
Yes, the implementation in the PR comes without any function call overhead. The logic to support the named parameters is purely implemented in the parser. The actual function implementation is provided to “round off” the implementation and because it was super simple to add. Best regards Tim Düsterhus

Larry Garfield

1 year ago
On Wed, May 21, 2025, at 9:13 AM, Tim Düsterhus wrote:
> Hi > > Am 2025-05-19 12:48, schrieb Volker Dusch: >> We're still looking for feedback on the ...variadic approach to the >> Syntax: >> https://wiki.php.net/rfc/clone_with_v2#open_issues, as we only got one >> reply so far on the topic. > > I was hoping for some additional opinions here before adding my own, but > since this does not appear to happen, adding my personal opinion on this > matter now: > > *Some* property name being completely incompatible with “clone with” (no > matter how the first parameter is going to be called) is a limitation > that should not exist, it feels like a feature that is broken by design > and I think I really would hate it if the documentation of this RFC > would need a “Caution: It is not possible to reassign a property called > '$object', due to a parameter name conflict”.
I completely disagree here. The __ prefix is sufficient to solve the issue. A double underscore prefix has been understood to mean "internal to PHP, magic here" for at least 20 years. The chances of someone having a property called $__object are virtually nil, and if they do, they're already treading on naming that's reserved for PHP's use anyway so if it breaks, it's their own fault.
> Adjusting the signature to `clone(object $object, array > $withProperties)` would not have this problem and I don't consider the > additional verbosity of an array literal to be a problem. Static > analysis tools already understand array shapes and would need > adjustments either way to understand the semantics. > > From an implementation PoV a regular array parameter would also be > simpler, since the implementation would be able to simply pass along the > input array, whereas the “variadic” syntax needs special handling to > combine positional parameters with named parameters into a single array > that is then used in the cloning process. > > Syntax-wise there might also be a middle-ground. Similarly to how this > RFC turns `clone()` into a function, the `array()` syntax also looks > like a function call and would naturally extend to named parameters. > While mixing positional and named parameters would probably get complex, > allowing purely named parameters would trivially be possible (without > any function call overhead). It would also allow using the > first-class-callable syntax with `array(...)`, something I would liked > to have in the past. A proof of concept PR is at: > > https://github.com/php/php-src/pull/18613 > > Combining named-parameter `array()` syntax with clone taking a array as > the second parameter would allow for the following, which might combine > the best of both worlds? > > clone($obj, array(foo: 1, bar: "baz", object: "this is not > blocked"));
I completely disagree here as well. I actively dislike using an array here, as I do find quoting all the keys to be cumbersome. And I don't think we should go with a worse syntax in the hopes of it being improved later, when there may well be other unforeseen challenges there. (Eg, I have no idea if bare array keys would cause issues with constants.) Moreover, we have 15 years of training and coding standards and automation tools that say to never use array(), always []. Reversing that would be far more disruptive for the ecosystem, even if it turns out to be not too disruptive to the code. I strongly prefer the current syntax, and if the RFC changed to require an array I would strongly consider voting No on those grounds alone. --Larry Garfield

Theodore Brown

1 year ago
On Wed, May 21, 2025 at 23:27 Larry Garfield wrote: > On Wed, May 21, 2025, at 9:13 AM, Tim Düsterhus wrote: >> Am 2025-05-19 12:48, schrieb Volker Dusch: >>> We're still looking for feedback on the ...variadic approach to the >>> Syntax: >>> https://wiki.php.net/rfc/clone_with_v2#open_issues, as we only got one >>> reply so far on the topic. >> >> ... >> *Some* property name being completely incompatible with “clone with” (no >> matter how the first parameter is going to be called) is a limitation >> that should not exist, it feels like a feature that is broken by design >> and I think I really would hate it if the documentation of this RFC >> would need a “Caution: It is not possible to reassign a property called >> '$object', due to a parameter name conflict”. > > I completely disagree here.  The __ prefix is sufficient to solve the issue. > A double underscore prefix has been understood to mean "internal to PHP, magic > here" for at least 20 years.  The chances of someone having a property called > $__object are virtually nil, and if they do, they're already treading on naming > that's reserved for PHP's use anyway so if it breaks, it's their own fault. Is the __ prefix part of the current proposal? Currently the RFC examples have the parameter name as simply `$object`. Even if the parameter is renamed to `$__object`, there would still have to be a warning in the docs that it isn't possible to use the function to set a property named `__object`, which I agree with Tim is a limitation that should not exist. For people who want to consistently use named arguments for all parameters, (rather than all parameters except the first one) the code would look like: $obj = clone( __object: $this, foo: 'bar', object: $baz, ); Is this API really better than with a simple array parameter? $obj = clone($this, [ 'foo' => 'bar', 'object' => $baz, ]); Using an array results in fewer lines of code, and the distinction between the object being cloned and the properties being set is much clearer. I get that it may be annoying to quote array keys, but to me this is not as bad as the confusing variadic API. In the future PHP could get a simpler array syntax like `[foo: 'bar']`, which would benefit far more functions than only `clone()`. Kind regards, Theodore

Volker Dusch

1 year ago
Version 1.1 Update: Array syntax over named arguments. Thank you everyone for the discussion and for improving this RFC. I'm very happy with the updates we made thanks to your feedback on and off list. The main idea of this RFC was to have as little of a footprint as possible and make it feel natural in PHP. I got carried away a bit during writing this, and by using named arguments, introduced something that isn't used in php-src provided functions, had edge cases that required documentation, and all in all, didn't feel natural in PHP but like an inconsistency. PHP uses arrays to pass lists everywhere else, so changing this here feels like too much scope for this little change. I'm very happy this was caught and raised in the discussions. Thank you. If there is desire for named-parameter-as-an-array syntax this should be standalone RFC with more scope than a single function. We've updated the RFC and the implementation, removing the "Open Issue" listed before, as this change resolved all of them. - https://wiki.php.net/rfc/clone_with_v2 - https://github.com/TimWolla/php-src/pull/6
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Nicolas Grekas

1 year ago
Hi Volker, Thanks for the update. Le lun. 26 mai 2025 à 16:05, Volker Dusch <volker@tideways-gmbh.com> a écrit :
> Version 1.1 Update: Array syntax over named arguments. > > Thank you everyone for the discussion and for improving this RFC. I'm very > happy with the updates we made thanks to your feedback on and off list. > > The main idea of this RFC was to have as little of a footprint as possible > and make it feel natural in PHP. I got carried away a bit during writing > this, and by using named arguments, introduced something that isn't used in > php-src provided functions, had edge cases that required documentation, and > all in all, didn't feel natural in PHP but like an inconsistency. PHP uses > arrays to pass lists everywhere else, so changing this here feels like too > much scope for this little change. I'm very happy this was caught and > raised in the discussions. Thank you. > > If there is desire for named-parameter-as-an-array syntax this should be > standalone RFC with more scope than a single function. > > We've updated the RFC and the implementation, removing the "Open Issue" > listed before, as this change resolved all of them. > > - https://wiki.php.net/rfc/clone_with_v2 > - https://github.com/TimWolla/php-src/pull/6 >
To me, it would have made more sense to still make "clone" an operator but I think we can all get used to the array style, so if the majority thinks arrays should be the way to go, so be it. I think the RFC is missing a few bits to be complete: - making "clone" a function means suddenly a "use clone;" or a "\clone" is going to be needed to not get a perf hit, isn't it? But since $y = clone $x; is still going to be valid, how will this be handled? The RFC could give some more hints on the topic. - writing code with a wide range of supported PHP versions means we'll start using construct such as: if (PHP_VERSION_ID>=80500) { $b = 'clone'($a, [...]); // note 'clone' is a string here, so that the line is valid syntax on PHP <= 8.4 else { // another code path for PHP 8.4 } That's of course because "use clone", "\clone" or "clone($foo)" is invalid PHP syntax currently. It could make sense to tell about this and show an example like this one in the RFC. - what if one gives a mangled property in the array? clone($foo, ["\0Foo\0bar" => 123]) It could be useful to write something about this case and/or at least have a test case that shows how this should behave. - To me, the main technical drawback of the RFC is that it builds on mandatory deep-cloning, which means wasted CPU/mem resources by design. This has to be mentioned in the RFC IMHO so that voters can make an informed decision. About this last point, it's the one that makes me undecided about my voting decision on the RFC. I shared my concern and a solution in another thread, where I also address the BC concern that is mentioned in the RFC. Since this concern has been addressed, I think this should be considered more closely. As is, that's a big omission to me - recognizing and addressing the issue with deep-cloning. Cheers, Nicolas

Tim Düsterhus

1 year ago
Hi Clarifying on the technical questions. Am 2025-05-26 16:37, schrieb Nicolas Grekas:
> I think the RFC is missing a few bits to be complete: > > - making "clone" a function means suddenly a "use clone;" or a "\clone" > is > going to be needed to not get a perf hit, isn't it? But since $y = > clone > $x; is still going to be valid, how will this be handled? The RFC could > give some more hints on the topic.
The implementation of making clone a function matches that of making `exit()` a function in PHP 8.4 (https://wiki.php.net/rfc/exit-as-function). Specifically, `clone` will remain a keyword and any use of a bare `clone` will compile to a fully-qualified call to the clone function, as if you would have written `\clone()`. For the same reason it will remain impossible to define a `clone()` function within a namespace. It will also be disallowed to use `disable_functions=clone` (the same as with exit and die). That `clone()` will become a function should be considered an implementation detail. You might start seeing a `clone()` frame in your stack traces, within `assert(false && clone($foo))` the `\clone()` call will be fully-qualified in the error message output and error messages might slightly change (e.g. when passing a non-object to clone). Other than that, it should not be noticeable. Perhaps even the ZEND_CLONE opcode will remain when using clone with a single-parameter (this is similarly to the other optimization of “special functions”, such as count() or sprintf()). I have made the following changes to the RFC that should hopefully clarify things: https://wiki.php.net/rfc/clone_with_v2?do=diff&rev2%5B0%5D=1748270099&rev2%5B1%5D=1748272162&difftype=sidebyside
> - writing code with a wide range of supported PHP versions means we'll > start using construct such as: > if (PHP_VERSION_ID>=80500) { > $b = 'clone'($a, [...]); // note 'clone' is a string here, so that > the > line is valid syntax on PHP <= 8.4 > else { > // another code path for PHP 8.4 > } > That's of course because "use clone", "\clone" or "clone($foo)" is > invalid > PHP syntax currently.
Only `use function clone;` is currently invalid syntax (and it will remain invalid syntax, similarly to `use function exit;`). `clone($a)` currently is a perfectly valid cloning statement (with redundant parentheses) and `\clone($a)` is a valid call to a function called `clone` that does not exist: https://3v4l.org/VB3j1 But as I said above, it is not necessary to change any existing code. The RFC is clear on that: There are no backwards incompatible changes regarding the usage. When finalizing the implementation, I'll make sure to run some benchmarks to determine whether or not keeping the OPcode is worth it for the “non-with” case, or whether making clone a “frameless” function would bring any benefit. But making sure it runs fast is the job of the engine, not the job of the PHP developer and generally speaking performance characteristics can change even without RFCs.
> It could make sense to tell about this and show an example like this > one in > the RFC.
Given that your question appears to be based on a misunderstanding, I believe adding such an example to the RFC is not useful.
> - what if one gives a mangled property in the array? clone($foo, > ["\0Foo\0bar" => 123]) > It could be useful to write something about this case and/or at least > have > a test case that shows how this should behave.
As the RFC specifies, clone-with behaves exactly like regular property assignments. This means your example behaves like `$foo->{"\0Foo\0bar"} = 123;` and thus will throw “Error: Cannot access property starting with "\0"”. I have added a test to the implementation [1]. Adding that to the RFC text itself is probably not useful to the average reader, since this is not something they will encounter. Best regards Tim Düsterhus [1] https://github.com/TimWolla/php-src/pull/6/commits/6f55e142e55af013bed0f31d7dc1633ef8201bff

Volker Dusch

1 year ago
Hi Nicolas, Thank you for the great email and for thinking this through. Getting input from the folks that maybe will use this feature the most is very valuable, and I appreciate that you're taking the time to do this early. On Mon, May 26, 2025 at 4:37 PM Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> - To me, the main technical drawback of the RFC is that it builds on > mandatory deep-cloning, which means wasted CPU/mem resources by design. > This has to be mentioned in the RFC IMHO so that voters can make an > informed decision. > > About this last point, it's the one that makes me undecided about my > voting decision on the RFC. > I shared my concern and a solution in another thread, where I also address > the BC concern that is mentioned in the RFC. Since this concern has been > addressed, I think this should be considered more closely. As is, that's a > big omission to me - recognizing and addressing the issue with deep-cloning. >
Quoting from your other email:
> Allow to skip deep-cloning of already updated properties (that's a
significant drawback of the current proposal - deep cloning before setting is a potential perf/mem hog built into the engine) : guarding deep-cloning with a strict comparison would be enough. To make sure we're on the same page: This would only come into effect if both a __clone method exists and clone($thing, $withProperties) is used. - We decided to __clone before updating properties to avoid BC issues. - For readonly classes, given their dependencies are likely to be readonly as well, they don't need to be deep cloned in most cases? - Therefore, I think combining __clone and clone-with will be a rare occasion, but all performance issues can be avoided by only using one of the two approaches in classes where that matters. - This RFC leaves future modifications to __clone open, so we could discuss a follow-up of passing the properties to __clone if we have a strategy to handle the BC issues, but for now, I see this as adding too much complexity for a very limited set of use cases that already are solved by the people that have them. So no situation will be worse than it was before, while some use-cases are enabled now and some performance critical code will probably stay the same as it is now.
> - writing code with a wide range of supported PHP versions means we'll
start using construct such as:
> if (PHP_VERSION_ID>=80500) { > $b = 'clone'($a, [...]); // note 'clone' is a string here, so that the
line is valid syntax on PHP <= 8.4
> else { > // another code path for PHP 8.4 > }
Tim has already addressed the \namespace fallback and that this doesn't apply here, and that no performance penalty is introduced into any existing codebases. So my personal suggestion would to be for code that is doing custom things during cloning and support PHP pre-clone-with as is and only refactor once 8.5 is the minimum requirement, in places where it improves the code. Kind Regards, Volker
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127

Nicolas Grekas

1 year ago
Hi Volker, Sorry for the delay in answering, it's been a long week-end AFK on my side. Le mer. 28 mai 2025 à 16:52, Volker Dusch <volker@tideways-gmbh.com> a écrit :
> Hi Nicolas, > > Thank you for the great email and for thinking this through. Getting input > from the folks that maybe will use this feature the most is very valuable, > and I appreciate that you're taking the time to do this early. > > On Mon, May 26, 2025 at 4:37 PM Nicolas Grekas < > nicolas.grekas+php@gmail.com> wrote: > >> - To me, the main technical drawback of the RFC is that it builds on >> mandatory deep-cloning, which means wasted CPU/mem resources by design. >> This has to be mentioned in the RFC IMHO so that voters can make an >> informed decision. >> >> About this last point, it's the one that makes me undecided about my >> voting decision on the RFC. >> I shared my concern and a solution in another thread, where I also >> address the BC concern that is mentioned in the RFC. Since this concern has >> been addressed, I think this should be considered more closely. As is, >> that's a big omission to me - recognizing and addressing the issue with >> deep-cloning. >> > > Quoting from your other email: > > > Allow to skip deep-cloning of already updated properties (that's a > significant drawback of the current proposal - deep cloning before setting > is a potential perf/mem hog built into the engine) : guarding deep-cloning > with a strict comparison would be enough. > > To make sure we're on the same page: This would only come into effect if > both a __clone method exists and clone($thing, $withProperties) is used. >
Correct. - We decided to __clone before updating properties to avoid BC issues.
>
Not sure which BC issues you've in mind, especially as that's a new feature. As I see it, before or after wouldn't change anything as far as __clone implementations are concerned.
> - For readonly classes, given their dependencies are likely to be readonly > as well, they don't need to be deep cloned in most cases? >
- Therefore, I think combining __clone and clone-with will be a rare
> occasion, but all performance issues can be avoided by only using one of > the two approaches in classes where that matters.
That's a very common mistake about readonly: it's also very useful for mutable classes. Here is an example that the Symfony Request/Response objects might follow one day: class Response { public function __construct( public string $content = '', public *readonly* ArrayObject $headers = new ArrayObject(), ) { } public function __clone() { $this->headers = clone $this->headers; } } $r1 = new Response(); $r2 = clone $r1; $r3 = clone($r1, ['headers' => new ArrayObject(['foo' => 'bar'])]); Note how making "headers" readonly binds the headers to its owner object. That's very useful since it forbids swapping the headers object by another one in response objects. I don't think this is/will be/should be a rare design, nor do I think this should be ignored in the design of the proposed RFC. The issue I highlighted previously is that __clone will "clone $this->headers" even if the resulting value will be immediately trashed on the line that creates $r3. This can cascade to nested clones of course depending on what is being cloned. That's where I see a waste of CPU/mem "by-design", which is unavoidable at the moment. - This RFC leaves future modifications to __clone open, so we could discuss
> a follow-up of passing the properties to __clone if we have a strategy to > handle the BC issues, but for now, I see this as adding too much complexity > for a very limited set of use cases that already are solved by the people > that have them. >
About the BC aspect I already shared a proposal that should avoid it. About the "very limited set of use cases", that's simply not true: people will start using the new syntax when consuming third-party classes, and authors of those classes will be "Sorry Outta Luck" when their users will start complaining about the perf hog I described, with no other options than telling "don't use that syntax, that perfhog is built-in"...
> So no situation will be worse than it was before, while some use-cases are > enabled now and some performance critical code will probably stay the same > as it is now. > > > - writing code with a wide range of supported PHP versions means we'll > start using construct such as: > > if (PHP_VERSION_ID>=80500) { > > $b = 'clone'($a, [...]); // note 'clone' is a string here, so that the > line is valid syntax on PHP <= 8.4 > > else { > > // another code path for PHP 8.4 > > } > > Tim has already addressed the \namespace fallback and that this doesn't > apply here, and that no performance penalty is introduced into any existing > codebases. >
So my personal suggestion would to be for code that is doing custom things
> during cloning and support PHP pre-clone-with as is and only refactor once > 8.5 is the minimum requirement, in places where it improves the code. >
I didn't answer Tim but he missed that in my example there is a second argument passed to clone(), which makes it a parse error currently. About the second aspect: if a class is designed with the API I gave as example above (that Response class), then the new syntax is going to be the only way to swap the headers while cloning. That means there will be cases where my snippet is going to be useful. I'm fine if you don't want to add it to the RFC of course - at least there's this thread for history and this could be documented later on somewhere else. Note that the new syntax being the only way to swap the headers while cloning means that all similarly-designed classes will *have to* live by the deep-cloning issue. As a corollary, authors that will want to avoid the deep-cloning issue will be forced to skip such designs. And that's a shame because that design looks desirable to me (and authors might realize the issue after-the-fact, which is even worse...) So what remains on my side is giving enough care to the deep-cloning case - see my previous messages of course. Nicolas

Tim Düsterhus

1 year ago
Hi Am 2025-06-03 16:24, schrieb Nicolas Grekas:
> - We decided to __clone before updating properties to avoid BC issues. >> > > Not sure which BC issues you've in mind, especially as that's a new > feature. > As I see it, before or after wouldn't change anything as far as __clone > implementations are concerned.
While clone-with is a new feature, it is automatically supported on every class. Currently calling `__clone()` is the first thing that happens after cloning the object and the implementation of `__clone()` might rely on the properties being unchanged from the original object. If the reassignment of properties would happen before the call to `__clone()` using clone-with with classes that have such a `__clone()` implementation might result in subtle and hard to debug issues.
> That's a very common mistake about readonly: it's also very useful for > mutable classes. Here is an example that the Symfony Request/Response > objects might follow one day: > > class Response > { > public function __construct( > public string $content = '', > public *readonly* ArrayObject $headers = new ArrayObject(), > ) { > } > > public function __clone() { > $this->headers = clone $this->headers; > } > } > > $r1 = new Response(); > $r2 = clone $r1; > $r3 = clone($r1, ['headers' => new ArrayObject(['foo' => 'bar'])]); > > Note how making "headers" readonly binds the headers to its owner > object. > That's very useful since it forbids swapping the headers object by > another > one in response objects.
I might lack the Symfony background, but it's not clear to me how preserving the object-identity, but not its contents is important here. I'd like to note, though: - Given that ArrayObject allows for interior mutability, you could just use `$r3->headers->exchangeArray(['foo' => 'bar']);`. - Exchanging the entire `ArrayObject` can only happen from within the class itself, since `$headers` is implicitly `protected(set)`. - You don't actually bind the `$headers` ArrayObject to the owner object, since the ArrayObject might be referenced by multiple Response objects, since passing it in the constructor is legal. The same would be true if it would be `public(set)`.
> The issue I highlighted previously is that __clone will "clone > $this->headers" even if the resulting value will be immediately trashed > on > the line that creates $r3. This can cascade to nested clones of course > depending on what is being cloned. That's where I see a waste of > CPU/mem > "by-design", which is unavoidable at the moment.
The same would be true if you reassign the property after the successful cloning.
> About the BC aspect I already shared a proposal that should avoid it. > About > the "very limited set of use cases", that's simply not true: people > will > start using the new syntax when consuming third-party classes, and > authors > of those classes will be "Sorry Outta Luck" when their users will start > complaining about the perf hog I described, with no other options than > telling "don't use that syntax, that perfhog is built-in"...
I don't see that happening, because it would mean that folks suddenly start modifying `public(set) readonly` properties during cloning. In other cases, the RFC does not enable anything new. For non-readonly properties, the user is already able to reassign (see above) and for non-public(set) properties, the class itself controls both `__clone()` and the property itself and thus can take the use-cases into account. On other words, what Volker already said here:
>> So no situation will be worse than it was before, while some use-cases >> are >> enabled now and some performance critical code will probably stay the >> same >> as it is now.
--------
> So my personal suggestion would to be for code that is doing custom > things >> during cloning and support PHP pre-clone-with as is and only refactor >> once >> 8.5 is the minimum requirement, in places where it improves the code. >> > > I didn't answer Tim but he missed that in my example there is a second > argument passed to clone(), which makes it a parse error currently.
In that case you can use: if (PHP_VERSION_ID >= 80500) { $b = \clone($a, ['foo' => 'bar']); } `\clone` is syntactically valid in PHP 8.0 or higher and a reference to the function `clone` in the global namespace. Incidentally this kind of cross-version compatibility is enabled by making `clone()` a function.
> About the second aspect: if a class is designed with the API I gave as > example above (that Response class), then the new syntax is going to be > the > only way to swap the headers while cloning. That means there will be > cases > where my snippet is going to be useful. I'm fine if you don't want to > add
If this syntax is the only way to swap headers while cloning, how would the branch for PHP 8.4 or lower look like? As Volker suggested, you can only switch to clone-with when PHP 8.5 is your minimum version, since you would need the “legacy way of doing things” for older PHP versions (and that one would still remain valid even with the changes this RFC proposes).
> So what remains on my side is giving enough care to the deep-cloning > case - > see my previous messages of course.
When building a solution it's important to understand the problem in depth and given that we don't currently understand the problem (or even see a problem), we are not in a position to build a solution. Importantly though the RFC leaves sufficient design space for a “Future Scope” solution (e.g. one that passes the $withProperties array to `__clone()` or the original object - which would enable your WeakMap use-case but is orthogonal to clone-with). I have just added that to the “Future Scope” section: https://wiki.php.net/rfc/clone_with_v2?do=diff&rev2%5B0%5D=1748959452&rev2%5B1%5D=1749034411&difftype=sidebyside Best regards Tim Düsterhus

Nicolas Grekas

1 year ago
Hi Le mer. 4 juin 2025 à 15:33, Tim Düsterhus <tim@bastelstu.be> a écrit :
> Hi > > Am 2025-06-03 16:24, schrieb Nicolas Grekas: > > - We decided to __clone before updating properties to avoid BC issues. > >> > > > > Not sure which BC issues you've in mind, especially as that's a new > > feature. > > As I see it, before or after wouldn't change anything as far as __clone > > implementations are concerned. > > While clone-with is a new feature, it is automatically supported on > every class. Currently calling `__clone()` is the first thing that > happens after cloning the object and the implementation of `__clone()` > might rely on the properties being unchanged from the original object. > If the reassignment of properties would happen before the call to > `__clone()` using clone-with with classes that have such a `__clone()` > implementation might result in subtle and hard to debug issues. >
This argument is totally symmetric: currently calling __clone is both the first and the last thing that happens. There can be implementations that rely on properties being unchanged after the call to __clone, eg: function __clone() { $this->foo = clone $this->foo; $this->fooId = spl_object_id($this->foo); } The example is silly but that's to highlight the symmetry. We spotted that passing $this to __clone would allow inspecting the changes and implementing deep-cloning protection, but only if calling __clone happens after. My concern is that as is, the RFC would block this possible future improvement - because then, it would be a non-BC behavior change.
> > That's a very common mistake about readonly: it's also very useful for > > mutable classes. Here is an example that the Symfony Request/Response > > objects might follow one day: > > > > class Response > > { > > public function __construct( > > public string $content = '', > > public *readonly* ArrayObject $headers = new ArrayObject(), > > ) { > > } > > > > public function __clone() { > > $this->headers = clone $this->headers; > > } > > } > > > > $r1 = new Response(); > > $r2 = clone $r1; > > $r3 = clone($r1, ['headers' => new ArrayObject(['foo' => 'bar'])]); > > > > Note how making "headers" readonly binds the headers to its owner > > object. > > That's very useful since it forbids swapping the headers object by > > another > > one in response objects. > > I might lack the Symfony background, but it's not clear to me how > preserving the object-identity, but not its contents is important here. > I'd like to note, though: > > - Given that ArrayObject allows for interior mutability, you could just > use `$r3->headers->exchangeArray(['foo' => 'bar']);`. > - Exchanging the entire `ArrayObject` can only happen from within the > class itself, since `$headers` is implicitly `protected(set)`. > - You don't actually bind the `$headers` ArrayObject to the owner > object, since the ArrayObject might be referenced by multiple Response > objects, since passing it in the constructor is legal. The same would be > true if it would be `public(set)`. >
Using ArrayObject was for a quick example. A stricter class can be implemented though but the deep-cloning issue would remain. "readonly" is also totally optional. I could have used a more trivial example: class Response { public ArrayObject $headers; public function __construct(array $headers) { $this->headers = new ArrayObject($headers); } public function __clone() { $this->headers = clone $this->headers; } }
> > The issue I highlighted previously is that __clone will "clone > > $this->headers" even if the resulting value will be immediately trashed > > on > > the line that creates $r3. This can cascade to nested clones of course > > depending on what is being cloned. That's where I see a waste of > > CPU/mem > > "by-design", which is unavoidable at the moment. > > The same would be true if you reassign the property after the successful > cloning. >
I was not suggesting that made any difference. This was about highlighting the deep-cloning issue.
> > About the BC aspect I already shared a proposal that should avoid it. > > About > > the "very limited set of use cases", that's simply not true: people > > will > > start using the new syntax when consuming third-party classes, and > > authors > > of those classes will be "Sorry Outta Luck" when their users will start > > complaining about the perf hog I described, with no other options than > > telling "don't use that syntax, that perfhog is built-in"... > > I don't see that happening, because it would mean that folks suddenly > start modifying `public(set) readonly` properties during cloning. In > other cases, the RFC does not enable anything new. For non-readonly > properties, the user is already able to reassign (see above) and for > non-public(set) properties, the class itself controls both `__clone()` > and the property itself and thus can take the use-cases into account. On > other words, what Volker already said here: > > >> So no situation will be worse than it was before, while some use-cases > >> are > >> enabled now and some performance critical code will probably stay the > >> same > >> as it is now. >
Fair, thanks for addressing this.
> -------- > > > So my personal suggestion would to be for code that is doing custom > > things > >> during cloning and support PHP pre-clone-with as is and only refactor > >> once > >> 8.5 is the minimum requirement, in places where it improves the code. > >> > > > > I didn't answer Tim but he missed that in my example there is a second > > argument passed to clone(), which makes it a parse error currently. > > In that case you can use: > > if (PHP_VERSION_ID >= 80500) { > $b = \clone($a, ['foo' => 'bar']); > } > > `\clone` is syntactically valid in PHP 8.0 or higher and a reference to > the function `clone` in the global namespace. Incidentally this kind of > cross-version compatibility is enabled by making `clone()` a function. >
Nice, I got lost in possibilities and missed that, thanks.
> > About the second aspect: if a class is designed with the API I gave as > > example above (that Response class), then the new syntax is going to be > > the > > only way to swap the headers while cloning. That means there will be > > cases > > where my snippet is going to be useful. I'm fine if you don't want to > > add > > If this syntax is the only way to swap headers while cloning, how would > the branch for PHP 8.4 or lower look like? As Volker suggested, you can > only switch to clone-with when PHP 8.5 is your minimum version, since > you would need the “legacy way of doing things” for older PHP versions > (and that one would still remain valid even with the changes this RFC > proposes). >
Let's leave this as such, I'm fine with a wait-n-see approach to that concern.
> > So what remains on my side is giving enough care to the deep-cloning > > case - > > see my previous messages of course. > > When building a solution it's important to understand the problem in > depth and given that we don't currently understand the problem (or even > see a problem), we are not in a position to build a solution. > Importantly though the RFC leaves sufficient design space for a “Future > Scope” solution (e.g. one that passes the $withProperties array to > `__clone()` or the original object - which would enable your WeakMap > use-case but is orthogonal to clone-with). I have just added that to the > “Future Scope” section: > > https://wiki.php.net/rfc/clone_with_v2?do=diff&rev2%5B0%5D=1748959452&rev2%5B1%5D=1749034411&difftype=sidebyside >
I think you've got the issue: deep-cloning. It's the only real thing I'm talking about and it's missing from the RFC - well, except in the future scope now, but that's a bit light to raise proper awareness about the design choice IMHO; Also, the change doesn't tell about "or the original object", I was surprised to see that difference between your email and the RFC update. Any reason for that? This relates to my call-ordering concern above. Cheers, Nicolas

Theodore Brown

1 year ago
On Mon, May 26, 2025 at 08:03 Volker Dusch wrote:   > Version 1.1 Update: Array syntax over named arguments. > > Thank you everyone for the discussion and for improving this RFC. > I'm very happy with the updates we made thanks to your feedback on and off list. > > The main idea of this RFC was to have as little of a footprint as possible and > make it feel natural in PHP. I got carried away a bit during writing this, and > by using named arguments, introduced something that isn't used in php-src > provided functions, had edge cases that required documentation, and all in all, > didn't feel natural in PHP but like an inconsistency. PHP uses arrays to pass > lists everywhere else, so changing this here feels like too much scope for this > little change. I'm very happy this was caught and raised in the discussions. > Thank you. > > If there is desire for named-parameter-as-an-array syntax this should be > standalone RFC with more scope than a single function. > > We've updated the RFC and the implementation, removing the "Open Issue" listed > before, as this change resolved all of them. > > - https://wiki.php.net/rfc/clone_with_v2 > - https://github.com/TimWolla/php-src/pull/6 Thank you Volker and Tim for your work on this RFC. It will be a great quality- of-life improvement for the language, and I look forward to voting in favor. Theodore

Volker Dusch

1 year ago
Hi everyone, As there was no additional feedback for the last 5 days, and we feel the RFC is in a good place, we intend to start voting on Wednesday if there are no new concerns raised. Thank you again! Volker On Mon, May 26, 2025 at 4:03 PM Volker Dusch <volker@tideways-gmbh.com> wrote:
> Version 1.1 Update: Array syntax over named arguments. > > Thank you everyone for the discussion and for improving this RFC. I'm very > happy with the updates we made thanks to your feedback on and off list. > > The main idea of this RFC was to have as little of a footprint as possible > and make it feel natural in PHP. I got carried away a bit during writing > this, and by using named arguments, introduced something that isn't used in > php-src provided functions, had edge cases that required documentation, and > all in all, didn't feel natural in PHP but like an inconsistency. PHP uses > arrays to pass lists everywhere else, so changing this here feels like too > much scope for this little change. I'm very happy this was caught and > raised in the discussions. Thank you. > > If there is desire for named-parameter-as-an-array syntax this should be > standalone RFC with more scope than a single function. > > We've updated the RFC and the implementation, removing the "Open Issue" > listed before, as this change resolved all of them. > > - https://wiki.php.net/rfc/clone_with_v2 > - https://github.com/TimWolla/php-src/pull/6 > > > -- > Volker Dusch > Head of Engineering > Tideways GmbH > Königswinterer Str. 116 > 53227 Bonn > https://tideways.io/imprint > > Sitz der Gesellschaft: Bonn > Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) > Registergericht: Amtsgericht Bonn, HRB 22127 >
-- Volker Dusch Head of Engineering Tideways GmbH Königswinterer Str. 116 53227 Bonn https://tideways.io/imprint Sitz der Gesellschaft: Bonn Geschäftsführer: Benjamin Außenhofer (geb. Eberlei) Registergericht: Amtsgericht Bonn, HRB 22127