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
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/6Kind 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
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
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
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
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
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 toclone()
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
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
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
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
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 inwhich 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
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
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 :)
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 toclone()
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
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/imprintSitz 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
[..]
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 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, 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 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 viawith()
. 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
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 viawith()
. 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
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 viawith()
. 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üsterhusHey 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
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 viawith()
. 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üsterhusHey 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
Yeah, the validation won't be too automatable (ie, in a base class) without at least having reusable hooks. It's important to be mindful that there are approximately three different paradigms when it comes to validating objects (and they apply to frameworks differently) in PHP.
-- validate on serialization --
This paradigm is mostly used in symfony via doctrine/serializer. An object is allowed to be in an "invalid" state and is usually constructed in a "zero" state, and then state is applied over the lifetime of the application. Only during serialization to the wire is it usually validated. So, it usually looks something like this:
$user = new User();
$user->name = "Rob"
$user->id = 123;
When you receive an object in a function, you will likely have to validate that specific properties are set before operating on it, otherwise you can end up with bugs. The nice thing about this style is that you can build up an object's state over a longer period (such as processing a form input or a database query result).
-- validate on construction --
This paradigm is commonly used in value objects and more functional-style PHP. The idea is that an object must be valid by the time it is constructed and does not allow partially formed objects to exist. All properties are validated in the constructor, and invalid combinations throw exceptions immediately. This tends to lead to more robust code, particularly when the object represents a meaningful invariant (e.g., Money
, EmailAddress
, Uuid
). Here's what it looks like:
$user = new User(name: "Rob", id: 123);
The dowside is that its harder to build objects piece by piece and usually requires factories, DTOs, and builder patterns for times when not all the data is available upfront. This approach is usually seen in functional, DDD (domain driven design), or strict typing contexts and plays nicely with immutability. This style is less-common in popular PHP frameworks like Symfony and Laravel, which tend to favor more flexible object construction.
-- validate on mutation --
This paradigm is popular in both Symfony and Laravel and favors an active-record-esqe approach. In this paradigm, each mutator (setter) is responsible for ensuring the property remains valid at the point of modification. The downside is that inter-property constraints require redundant checks that can be difficult to maintain. The nice thing is that they're easy to enforce.
Of course, these can be mixed-and-matched as needed/desired. The downside with the cloning method here is that it really puts "validation on construction" on a back foot. Developers will likely have absolutely no way to perform validation without rewriting their entire class structures and will make validation during construction basically impossible for immutable objects.
— Rob
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
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
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:
- A public or protected readonly property is initialised in a
constructor marked "final" - 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]
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:
- A public or protected readonly property is initialised in a
constructor marked "final"- A sub-class adds code that uses "clone with" to set that property to
a new valueThe 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
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
Le jeu. 15 mai 2025 à 16:06, Larry Garfield larry@garfieldtech.com a
écrit :
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
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 :
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 usefunc_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
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 :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 usefunc_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
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 :
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.
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 usefunc_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
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 :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 reallyarray_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 usefunc_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
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=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
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=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 $withPropertiesWe'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=revisionsKind 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
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
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
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 turnsclone()
into a function, thearray()
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 witharray(...)
, 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
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()
.
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 turnsclone()
into a function, thearray()
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 witharray(...)
, 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
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 forclone()
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
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 turnsclone()
into a function, thearray()
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 witharray(...)
, 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
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