Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127404 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id C685D1A00BC for ; Mon, 19 May 2025 17:06:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1747674250; bh=j8Adv5npfq66ZilwstQfLsvkRfZfou13NQPfK7FjtFo=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=JgYvIv3dgnDQh+5xNxiL3wgG+0Abw53JQon7HDLeInEvBFkImPJHL+tMBBnLURQub h559cyhQAKEJYN1LuF0ZwNxmUbzUX1gtg2pVvKGK0MbZwFmLr0+Jvs4WaXyPPj0j4/ 0tiEhLIi9IZO/MytNPRWBwiOQQQE4Ria9I5UP4CL7Bx0shWuznlkViF7DoOstAhUM1 8VWohdQD+Ti6V4DGkicmo0B047CPjoB2NqMtQQwlSIPFb2fRtMDkKYUgz7zAxulWyW qi7Cw41V4iuf6qwRMc57XLcwJ5M8K6F3DppKuVbBQWOBDqpmhhJBCIJDHpV+Zts3wU AlfsGIg8wi/Yg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 4BF30180059 for ; Mon, 19 May 2025 17:04:07 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from mail-yb1-f173.google.com (mail-yb1-f173.google.com [209.85.219.173]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Mon, 19 May 2025 17:04:07 +0000 (UTC) Received: by mail-yb1-f173.google.com with SMTP id 3f1490d57ef6-e7ba37b2b5bso1261273276.0 for ; Mon, 19 May 2025 10:06:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dqxtech.net; s=google; t=1747674375; x=1748279175; darn=lists.php.net; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=LQucmmEQD6HHI+M0qXro+iUW7ahn3MA7DtW3KelIEAA=; b=IYp3RtcORDBISS+2omSMn2S03o+msTloC2pQIUY58hGh/edw3rBPVpba/1FNJLiwVj v6h61T1RruoExXzCHxS7RxDIQPCnBm6HSFiabsAM+NFSf19CkczEb48eYETFj87aslGn y15eE2xImPelBr8YYahBtPuj1++hNduG3kCi165z0WNm1iuXvMTwCc7Hy/bIbyonIiYV 7nXoHX8jYr/LNFtxgshvZPBIHIrmj4rDAiE3ilKUDXwdWmdcfntLAK0n74Z1blIedzwn BbtL4QupF/WTHf9j34J8qKV8tdt4kmV+vuvhSA0Zf+lrpJVpAxL0GKVLdgDOWY0sCh33 qnsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747674375; x=1748279175; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=LQucmmEQD6HHI+M0qXro+iUW7ahn3MA7DtW3KelIEAA=; b=fJd76YEiap6YEv1ML/QGybl4Nsn0EUAXtB3uPBtI7f79RBHdqq8L+Kqu05cUig/Y99 VxIOOdRs1DN+h5WB53GAmGGs8IbM4N3tghf71nzEm9bV8WyiKs7RwO+5ecNC/UhyM7Vo Fv6ND8DlYLjcEVPJBGVftHn05qbIUtsl9123F/MkJM8Uj+JfuCM0cr+yOD8a5i+jPWNZ 9tQ7AYbCjJjVFis6OB8b4sdLx/LEcK6r7h+DEKCQXf3ZGWiEPzi9jUOYlWzGx5G7VJXO joMlP3IGqOTOY5aLhyX1ioybTQGHzhbbywDp+fg6yejlZ/jai9DdRCXulBmVdllpKs4s haqQ== X-Forwarded-Encrypted: i=1; AJvYcCXYDTCIjFlic4AFZzHgN8pWYKKZOrdGcI6mzf4F4N11uBTQM/Xjjz3xz0qzDSzmohAQFhsraSAvvnI=@lists.php.net X-Gm-Message-State: AOJu0YzS17rrhjA4Eml4f2ylQbHqhodsiE/amansKLqr+Sate4vkYWk0 GNaQgd2Dr3J9HR97I6PcnhhnrazHldOvdu4YIx3EComRsa4r0KFAZcf0Vq8XZMlYMi5azQnf3HL xbC9q5YxR+G2LNGSHD2rsMYyTx601EF08cIbQ9km7kw== X-Gm-Gg: ASbGncs5AtTTTJtv24mOC98f5LJu161CcbxWLUlk/tdwUL/6/7MR2bo96DJYhoxvKAO 6t9tmcMb45tJq4szMVoxJQ6gb4b1spPDvmcr+hEtc6M3R+ehJKEGj3OzB8TvwHYEdgcJpMsTQNe NY3zsKrF2+Z/BRI9rTgKju3oATQ/1RZqdFFglrAqutjRE0QqVJO4I9k+0ymActJPjlLg== X-Google-Smtp-Source: AGHT+IEVl+CSkpogwwgCLxRZ1a4pN4KmPUlsg6lE4lIRouCypLg+4F1C2x6cMjZbq9AstoLsmmL1Bz9k+KCXgOPtXSA= X-Received: by 2002:a05:6902:140c:b0:e75:c6a6:ca8 with SMTP id 3f1490d57ef6-e7b6d718fb5mr16087428276.45.1747674374861; Mon, 19 May 2025 10:06:14 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Mon, 19 May 2025 19:06:03 +0200 X-Gm-Features: AX0GCFsTJL5d3Qwvxbibh5UPHs1MxOaUHlyCaJ_fmbSq4rFYrRjKVBxjqRCyXCo Message-ID: Subject: Re: [PHP-DEV] [RFC] Clone with v2 To: Nicolas Grekas Cc: Larry Garfield , php internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable From: andreas@dqxtech.net (Andreas Hennings) On Mon, 19 May 2025 at 17:13, Nicolas Grekas wrote: > > > > Le lun. 19 mai 2025 =C3=A0 16:30, Andreas Hennings = a =C3=A9crit : >> >> On Fri, 16 May 2025 at 21:59, Nicolas Grekas >> wrote: >> > >> > >> > >> > Le jeu. 15 mai 2025 =C3=A0 16:06, Larry Garfield a =C3=A9crit : >> >> >> >> On Thu, May 15, 2025, at 1:22 AM, Stephen Reay wrote: >> >> >> >> > I may be missing something here.. >> >> > >> >> > So far the issues are "how do we deal with a parameter for the actu= al >> >> > object, vs new properties to apply", "should __clone be called bef= ore >> >> > or after the changes" and "this won't allow regular readonly proper= ties >> >> > 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 t= he >> >> > 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-example= s >> >> >> >> What that article calls "initonly" is essentially what became readonl= y. The second example is roughly what this RFC would look like if the extr= a 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) synt= ax is the cleanest solution. Given that experimentation, I would not suppo= rt an implementation that passes args to __clone and makes the developer fi= gure it out. That just makes a mess. >> >> >> >> Rob makes a good point elsewhere in thread that running __clone() aft= erward 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 t= o reject the changes other than throwing an exception, though in fairness t= he same is true of set hooks. Which also begs the question of whether a se= t hook would be sufficient that __clone() doesn't need to do extra validati= on? At least in the typical case? >> >> >> >> One possibility (just brainstorming) would be to update first, then c= all __clone(), but give clone a new optional arg that just tells it what pr= operties were modified by the clone call. It can then recheck just those p= roperties or ignore it entirely, as it prefers. If that handles only compl= ex cases (eg, firstName was updated so the computed fullName needs to be up= dated) and set hooks handle the single-property ones, that would probably c= over all bases reasonably well. >> > >> > >> > I like where this is going but here is a variant that'd be even more c= apable: >> > >> > 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 =3D $values['x'] * 10; >> // Prevent that the same property is assigned again. >> unset($values['x']); >> } >> } >> } >> >> $obj =3D new C(5, 7, 9); >> $clone =3D clone($obj, x: 2, y: 3); >> assert($clone->x =3D=3D=3D 20); // x was update in __clone(). >> assert($clone->y =3D=3D=3D 3); // y was auto-updated after __clone(). >> assert($clone->z =3D=3D=3D 9); // z was not touched at all. > > > I'm not sure I understand, there might be missing bits to your idea, eg w= here 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 sign= ificant 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 =3D=3D=3D $old->prop to se= e 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 !=3D=3D $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 ob= jects 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 (trans= forming 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 s= ignificant drawback of the current proposal - deep cloning before setting i= s a potential perf/mem hog built into the engine) : guarding deep-cloning w= ith 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 somet= hing I've always found very limiting when using weak-map: let's say some me= tadata are stored about object $foo in a weakmap, it's currently not possib= le to track those metadata across clones without using some nasty tricks. I= f __clone were given the original object, it's be easy to duplicate meta-da= ta 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 "objec= t" argument, or zero. >> >> This seems reasonable. >> > > I'd be happy to know what Volker and Tim think about this? I read they ex= cluded any change to __clone in the RFC, but I think it should still be pos= sible to discuss this, especially if it provides the path to the desired so= lution. > > Nicolas