Re: [RFC] Clone with v2

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

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


Thread (26 messages)

« previous php.internals (#127388) next »