[PHP-DEV] [RFC] Clone with v2

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

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.

PHP: rfc:clone_with_v2

We are actively looking for some feedback on
PHP: rfc:clone_with_v2, 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 Clone with by TimWolla · Pull Request #6 · TimWolla/php-src · GitHub

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. :slight_smile: 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

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. :slight_smile: 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 :slight_smile:

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

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

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

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

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

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

···

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

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

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

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

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 PHP: rfc:readonly_amendments, 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

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.

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

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
PHP: rfc:readonly_amendments,
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

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

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

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

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

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

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

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

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