Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124424 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 qa.php.net (Postfix) with ESMTPS id AA1F71A00B7 for ; Mon, 15 Jul 2024 08:23:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1721031925; bh=gqwPRCiOrXSlHHqpSimvVPFKU3d7BwBfnY7dTdnQybg=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=NxW6myfXS5/2UyahC+RkRPWFG1xntF/Zy/jL3KNLdJDlHyP5LaKFEGSKcoQkS5SFU AHS8SK484RBn3GE9n3J4Y2DGo/lG86Z0FRsL0YLPb0UPyvRcgOi/t9px+lSD/shtrQ VCHcC2aqlBI6pbFkX7KoV+dp4b5UWVYlISfq3t/KiyC07rYF3CiohRoFEjraDE5bR5 QV44y6aYl2+yaa8Kyv0ohuhkWY5dx9lPZJN61Q0pxLhnwUeMTL2771jfE/nsdb2wSv rpOmFTaDlnzc0RzxSvOQDFkPHD+nFk+ns8l1D4HNGHOUtTycjDN4Wk3L3Nc6C6ZHWL DkmaeRNwbSwwg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 095931801EF for ; Mon, 15 Jul 2024 08:25:24 +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,FREEMAIL_FROM, HTML_MESSAGE,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: No X-Envelope-From: Received: from mail-lf1-f47.google.com (mail-lf1-f47.google.com [209.85.167.47]) (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, 15 Jul 2024 08:25:23 +0000 (UTC) Received: by mail-lf1-f47.google.com with SMTP id 2adb3069b0e04-52e991fb456so3871268e87.0 for ; Mon, 15 Jul 2024 01:23:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1721031833; x=1721636633; darn=lists.php.net; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=jIS/+hmmtnEwWzRyVVqM/4QI/9M3OkGam0hgOZxECWM=; b=fAwMnCjXWuO+2471srorA16E0XKv3285MtQR6h0UZ2jXSOAyU8KDYPNL4uamN1Je1p 5Gq6XsQlNEmQezNSqCWa9ykZhraF+vdDe/thDMn3lbApkzSl9cxEZU2gPsv6995FdlLn mHSLdP0nK14Lf4CdUTMqzKdAWosu39sV65DRJhkZLPUjokv12wpDjZU6v+htQFgNTQRv slp3tQ3QVX38Lyg5NH44aWlU0peHXbsOksGGWHS7y8h3FSROveiUja974X/HInylI3UL prP17dApMEaSnnEc0v/f7rSv426UqZjuRHTFWPDbsLAEA2IZ2ayZESid9+Sve98n2MQY VtPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1721031833; x=1721636633; h=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=jIS/+hmmtnEwWzRyVVqM/4QI/9M3OkGam0hgOZxECWM=; b=Acji6dQyO6YWbtZKs3CNXmOwmUvho1WXE6ytLxlN4n1a/1wFOlyfx7BJkNErxQcxW1 aJhQ3oB536skrONyuI3jOyMC3zIfbVsNNCQhgtRezQboEY8WAUxW4pRW3/99YgRNaUoF yK8B7pEdJoBydzj+6JlGYCbnRtkyq/7IYLfS48Fua9K/LN+9QD7MrOPOmj08QOOWFIaO UU0ohLn770P/pxwkTrE6WOD5+F/F2G1HbRccbrWYD23VlkQaGiH0pIh/oC07UexkXHre CsKN+HLTd3BV777Mq4eCOC2beAwAbFfHP0YHzqu6a4u8IxQg1Tkxdu7qAIsCl+OJ7Vq6 ENFA== X-Gm-Message-State: AOJu0YwamDArnBh1+fIPhm2gPguuUXj3m2DfjyjX9prW5zqO+H5hvnSq juotRT9/UwhPqpdfdQbrktGMdjMxdhM5qrXt51V4yN37zid4mQI4Hd+nI0jMzdNNUtnzJue1dBU UgnarZrskwhsIJGeNExixB8hJq4Y= X-Google-Smtp-Source: AGHT+IH5BdKUrdeFTabiWuRSQW62mmv44lcMJDLP9VUBu4errJILqkgQMauRDMAqfDYG0pXVMIWm6mVu4PP+hso7aQE= X-Received: by 2002:a05:6512:3d94:b0:52b:c1cf:cecb with SMTP id 2adb3069b0e04-52ecb69dc13mr2832467e87.24.1721031832749; Mon, 15 Jul 2024 01:23:52 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 References: <1118bbcd-a7b4-47bf-bf35-1a36ab4628e1@bastelstu.be> <45847b93-02bf-459f-bcd2-81ba35a12c24@bastelstu.be> <46bd4098-2936-4e46-98e9-fe55118325c2@bastelstu.be> <61ab36bc-b045-452a-84e0-87367d4c680e@bastelstu.be> In-Reply-To: <61ab36bc-b045-452a-84e0-87367d4c680e@bastelstu.be> Date: Mon, 15 Jul 2024 10:23:40 +0200 Message-ID: Subject: Re: [PHP-DEV] [RFC] Lazy Objects To: =?UTF-8?Q?Tim_D=C3=BCsterhus?= Cc: PHP Internals List Content-Type: multipart/alternative; boundary="00000000000009cd1c061d44f303" From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --00000000000009cd1c061d44f303 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Le jeu. 11 juil. 2024 =C3=A0 20:31, Tim D=C3=BCsterhus a= =C3=A9crit : > Hi > > On 7/11/24 10:32, Nicolas Grekas wrote: > >> Many things are already possible in userland. That does not always mea= n > >> that the cost-benefit ratio is appropriate for inclusion in core. I ge= t > >> behind the two examples in the =E2=80=9CAbout Lazy-Loading Strategies= =E2=80=9D section, > >> but I'm afraid I still can't wrap my head why I would want an object > >> that makes itself lazy in its own constructor: I have not yet seen a > >> real-world example. > >> > > > > Keeping this capability for userland is not an option for me as it woul= d > > mostly defeat my goal, which is to get rid of any userland code on this > > topic (and is achieved by the RFC). > > > > Here is a real-world example: > > > https://github.com/doctrine/DoctrineBundle/blob/2.12.x/src/Repository/Laz= yServiceEntityRepository.php > > > > This class currently uses a poor-man's implementation of lazy objects a= nd > > would greatly benefit from resetAsLazyGhost(). > > > > Sorry, I was probably a little unclear with my question. I was not > specifically asking if anyone did that, because I am fairly sure that > everything possible has been done before. > > I was interested in learning why I would want to promote a > "LazyServiceEntityRepository" instead of the user of my library just > making the "ServiceEntityRepository" lazy themselves. > > I understand that historically making the "ServiceEntityRepository" lazy > yourself would have been very complicated, but the new RFC makes this > super easy. > > So based on my understanding the "LazyServiceEntityRepository" > (c|sh)ould be deprecated with the reason that PHP 8.4 provides all the > necessary tools to do it yourself, no? That would also match your goal > of getting rid of userland code on this topic. > That class is not a candidate for deprecation even after this RFC. But thanks to this RFC, PHP 8.4 would allow removing all the code related to the magic methods in the current implementation. > To me this is what the language evolution should do: Enable users to do > things that previously needed to be provided by userland libraries, > because they were complicated and fragile, not enabling userland > libraries to simplify things that they should not need to provide in the > first place because the language already provides it. > That's exactly it: instead of using a third party lib (or in this case implementing a poor man's subset of a correct lazy object implementation), the engine would enable using a native feature to achieve a fully correct behavior in a very simple way. In this case, LazyServiceEntityRepository would directly use ReflectionClass::resetAsLazyGhost to make itself lazy, so that its consumers get a packaged behavior and don't need to care about the topic while consuming the class. > >> I have one question regarding the updated initialization sequence. The > >> RFC writes: > >> > >>> Properties that are declared on the real instance are uninitialized o= n > >> the proxy instance (including overlapping properties used with > >> ReflectionProperty::skipLazyInitialization() or > >> setRawValueWithoutLazyInitialization()) to synchronize the state share= d > by > >> both instances. > >> > >> I do not understand this. Specifically I do not understand the "to > >> synchronize the state" bit. > > > > > > We reworded this sentence a bit. Clearer? > > Yes, I think it is clearer. Let me try to rephrase this differently to > see if my understanding is correct: > > --- > > For every property on that exists on the real instance, the property on > the proxy instance effectively [1] is replaced by a property hook like > the following: > > public PropertyType $propertyName { > get { > return $this->realInstance->propertyName; > } > set(PropertyType $value) { > $this->realInstance->propertyName =3D $value; > } > } > > And value that is stored in the property will be freed (including > calling the destructor if it was the last reference), as if `unset()` > was called on the property. > > [1] No actual property hook will be created and the `realInstance` > property does not actually exist, but the semantics behave as if such a > hook would be applied. > Conceptually, you've got it right yes! > >> My understanding is that the proxy will > >> always forward the property access, so there effectively is no state o= n > >> the proxy?! > > > > > > It follows that more properties can exist on the proxy itself (declared > by > > child classes of the real object that the proxy implements). > > > > Right, that's mentioned in (2), so all clear. > > > > >>> That is very true. I had a look at the userland implementation and > >> indeed, > >>> we keep the wrapper while cloning the backing instance (it's not that > we > >>> have the choice, the engine doesn't give us any other options). > >>> RFC updated. > >>> > >>> We also updated the behavior when an uninitialized proxy is cloned: w= e > >> now > >>> postpone calling $real->__clone to the moment where the proxy clone i= s > >>> initialized. > >> > >> Do I understand it correctly that the initializer of the cloned proxy = is > >> effectively replaced by the following: > >> > >> function (object $clonedProxy) use ($originalProxy) { > >> return clone $originalProxy->getRealObject(); > >> } > >> > > > > Nope, that's not what we describe in the RFC so I hope you can read it > > again and get where you were confused and tell us if we're not clear > enough > > (to me we are :) ) > > The "cloning of the real instance" bit is what lead me to this > understanding. > > > The $originalProxy is *not* shared with $clonedProxy. Instead, it's > > *initializers* that are shared between clones. > > And then, when we call that shared initializer in the $clonedProxy, we > > clone the returned instance, so that even if the initializer returns a > > shared instance, we don't share anything with the $originalProxy. > > > > Ah, so you mean if the initializer would look like this instead of > creating a fresh object within the initializer? > > $predefinedObject =3D new SomeObj(); > $myProxy =3D $r->newLazyProxy(function () use ($predefinedObject) { > return $predefinedObject; > }); > $clonedProxy =3D clone $myProxy; > $r->initialize($myProxy); > $r->initialize($clonedProxy); > > It didn't even occur to me that one would be able to return a > pre-existing object: I assume that simply reusing the initializer would > create a separate object and that would be sufficient to ensure that the > cloned instance would be independent. > E.g. Doctrine's Entity Manager returns always the same instance. > >> ? Then I believe this is unsound. Consider the following: > >> > >> $myProxy =3D $r->newLazyProxy(...); > >> $clonedProxy =3D clone $myProxy; > >> $r->initialize($myProxy); > >> $myProxy->someProp++; > >> var_dump($clonedProxy->someProp); > >> > >> The clone was created before `someProp` was modified, but it outputs t= he > >> value after modification! > >> > >> Also: What happens if the cloned proxy is initialized *before* the > >> original proxy? There is no real object to clone. > >> > >> I believe the correct behavior would be: Just clone the proxy and keep > >> the same initializer. Then both proxies are actually fully independent > >> after cloning, as I would expect from the clone operation. > >> > > > > That's basically what we do and what we describe in the RFC, just with > the > > added lazy-clone operation on the instance returned by the initializer. > > > > This means that if I would return a completely new object within the > initializer then for a cloned proxy the new object would immediately be > cloned and the original object be destructed, yes? > That's correct. This preserves the lifecycle of cloned objects: __clone() has to be called when creating clones if the method exists. > Frankly, thinking about this cloning behavior gives me a headache, > because it quickly leads to very weird semantics. Consider the following > example: > > $predefinedObject =3D new SomeObj(); > $initializer =3D function () use ($predefinedObject) { > return $predefinedObject; > }; > $myProxy =3D $r->newLazyProxy($initializer); > $otherProxy =3D $r->newLazyProxy($initializer); > $clonedProxy =3D clone $myProxy; > $r->initialize($myProxy); > $r->initialize($otherProxy); > $r->initialize($clonedProxy); > > To my understanding both $myProxy and $otherProxy would share the > $predefinedObject as the real instance and $clonedProxy would have a > clone of the $predefinedObject at the time of the initialization as its > real instance? > Correct. The sharing is not specifically related to cloning. But when cloning happens, the expected behavior is well defined: we should have separate states. > To me this sounds like cloning an uninitialized proxy would need to > trigger an initialization to result in semantics that do not violate the > principle of least astonishment. > Forcing an initialization when cloning would be unexpected. E.g. in Doctrine, when you clone an uninitialized entity, you don't trigger a database roundtrip. Instead, you create a new object that still references the original state internally, but under a different object identity. This cloning behavior is the one we've had for more than 10 years and I think it's also the least astonishing one - at least if we consider this example as a real world trial of this principle. > I would assume that cloning a proxy is something that rarely happens, > because my understanding is that proxies are most useful for service > objects, whereas ghost objects would be used for entities / value > objects, so this should not be too much of a problem. > I share this opinion. Then, since the difference between ghost vs proxy should be internal from a consumer PoV, defining an observable behavior for ghost objects also defines the behavior for proxy ones. > >> 2. > >> > >> > Properties are not initialized to their default value yet (they ar= e > >> initialized before calling the initializer). > >> > >> I see that you removed the bit about this being not observable. What i= s > >> the reason that you removed that? One possible reason that comes to my > >> mind is a default value that refers to a non-existing constant. It wou= ld > >> be observable because the initialization emits an error. Are there any > >> other reasons? > >> > > > > That's because this is observable using e.g. (array) or var_dump. > > > > I see. Perhaps add a short sentence with the reasoning. Something like: > > Properties are not initialized to their default value yet (they are > initialized before calling the initializer). As an example, this has an > impact on the behavior of an (array) cast on uninitialized objects and > also when the default value is based on a constant that is not yet > defined when creating the lazy object, but will be defined at the point > of initialization. > Thanks for the proposal. I've added it to the RFC. Cheers, Nicolas --00000000000009cd1c061d44f303 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


=
Le=C2=A0jeu. 11 juil. 2024 =C3=A0=C2= =A020:31, Tim D=C3=BCsterhus <tim@bastelstu.be> a =C3=A9crit=C2=A0:
Hi

On 7/11/24 10:32, Nicolas Grekas wrote:
>> Many things are already possible in userland. That does not always= mean
>> that the cost-benefit ratio is appropriate for inclusion in core. = I get
>> behind the two examples in the =E2=80=9CAbout Lazy-Loading Strateg= ies=E2=80=9D section,
>> but I'm afraid I still can't wrap my head why I would want= an object
>> that makes itself lazy in its own constructor: I have not yet seen= a
>> real-world example.
>>
>
> Keeping this capability for userland is not an option for me as it wou= ld
> mostly defeat my goal, which is to get rid of any userland code on thi= s
> topic (and is achieved by the RFC).
>
> Here is a real-world example:
> https://github.com/doctrine/DoctrineBundle/blob/2.12.x/src/Repository= /LazyServiceEntityRepository.php
>
> This class currently uses a poor-man's implementation of lazy obje= cts and
> would greatly benefit from resetAsLazyGhost().
>

Sorry, I was probably a little unclear with my question. I was not
specifically asking if anyone did that, because I am fairly sure that
everything possible has been done before.

I was interested in learning why I would want to promote a
"LazyServiceEntityRepository" instead of the user of my library j= ust
making the "ServiceEntityRepository" lazy themselves.

I understand that historically making the "ServiceEntityRepository&quo= t; lazy
yourself would have been very complicated, but the new RFC makes this
super easy.

So based on my understanding the "LazyServiceEntityRepository" (c|sh)ould be deprecated with the reason that PHP 8.4 provides all the
necessary tools to do it yourself, no? That would also match your goal
of getting rid of userland code on this topic.

That class is not a candidate for deprecation even after this RFC. = But thanks to this RFC, PHP 8.4 would allow removing all the code related t= o the magic methods in the current implementation.

=
=C2=A0
To me this is what the language evolution should do: Enable users to do things that previously needed to be provided by userland libraries,
because they were complicated and fragile, not enabling userland
libraries to simplify things that they should not need to provide in the first place because the language already provides it.
=
That's exactly it: instead of using a third party lib (o= r in this case implementing a poor man's subset of a correct lazy objec= t implementation), the engine would enable using a native feature to achiev= e a fully correct behavior in a very simple way. In this case, LazyServiceE= ntityRepository would directly use ReflectionClass::resetAsLazyGhost to mak= e itself lazy, so that its consumers get a packaged behavior and don't = need to care about the topic while consuming the class.

<= /div>
=C2=A0
>> I have one question regarding the updated initialization sequence.= The
>> RFC writes:
>>
>>> Properties that are declared on the real instance are uninitia= lized on
>> the proxy instance (including overlapping properties used with
>> ReflectionProperty::skipLazyInitialization() or
>> setRawValueWithoutLazyInitialization()) to synchronize the state s= hared by
>> both instances.
>>
>> I do not understand this. Specifically I do not understand the &qu= ot;to
>> synchronize the state" bit.
>
>
> We reworded this sentence a bit. Clearer?

Yes, I think it is clearer. Let me try to rephrase this differently to
see if my understanding is correct:

---

For every property on that exists on the real instance, the property on the proxy instance effectively [1] is replaced by a property hook like
the following:

=C2=A0 =C2=A0 =C2=A0public PropertyType $propertyName {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0get {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return $this->realInstan= ce->propertyName;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0set(PropertyType $value) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$this->realInstance->= propertyName =3D $value;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0}

And value that is stored in the property will be freed (including
calling the destructor if it was the last reference), as if `unset()`
was called on the property.

[1] No actual property hook will be created and the `realInstance`
property does not actually exist, but the semantics behave as if such a hook would be applied.

Conceptually, yo= u've got it right yes!

=C2=A0
>> My understanding is that the proxy will
>> always forward the property access, so there effectively is no sta= te on
>> the proxy?!
>
>
> It follows that more properties can exist on the proxy itself (declare= d by
> child classes of the real object that the proxy implements).
>

Right, that's mentioned in (2), so all clear.

>
>>> That is very true. I had a look at the userland implementation= and
>> indeed,
>>> we keep the wrapper while cloning the backing instance (it'= ;s not that we
>>> have the choice, the engine doesn't give us any other opti= ons).
>>> RFC updated.
>>>
>>> We also updated the behavior when an uninitialized proxy is cl= oned: we
>> now
>>> postpone calling $real->__clone to the moment where the pro= xy clone is
>>> initialized.
>>
>> Do I understand it correctly that the initializer of the cloned pr= oxy is
>> effectively replaced by the following:
>>
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0function (object $clonedProxy) use ($ori= ginalProxy) {
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return clone $originalProx= y->getRealObject();
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0}
>>
>
> Nope, that's not what we describe in the RFC so I hope you can rea= d it
> again and get where you were confused and tell us if we're not cle= ar enough
> (to me we are :) )

The "cloning of the real instance" bit is what lead me to this understanding.

> The $originalProxy is *not* shared with $clonedProxy. Instead, it'= s
> *initializers* that are shared between clones.
> And then, when we call that shared initializer in the $clonedProxy, we=
> clone the returned instance, so that even if the initializer returns a=
> shared instance, we don't share anything with the $originalProxy.<= br> >

Ah, so you mean if the initializer would look like this instead of
creating a fresh object within the initializer?

=C2=A0 =C2=A0 =C2=A0 $predefinedObject =3D new SomeObj();
=C2=A0 =C2=A0 =C2=A0 $myProxy =3D $r->newLazyProxy(function () use ($pre= definedObject) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return $predefinedObject;
=C2=A0 =C2=A0 =C2=A0 });
=C2=A0 =C2=A0 =C2=A0 $clonedProxy =3D clone $myProxy;
=C2=A0 =C2=A0 =C2=A0 $r->initialize($myProxy);
=C2=A0 =C2=A0 =C2=A0 $r->initialize($clonedProxy);

It didn't even occur to me that one would be able to return a
pre-existing object: I assume that simply reusing the initializer would create a separate object and that would be sufficient to ensure that the cloned instance would be independent.

E= .g. Doctrine's Entity Manager returns always the same instance.

=C2=A0
>> ? Then I believe this is unsound. Consider the following:
>>
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$myProxy =3D $r->newLazyProxy(...); >>=C2=A0 =C2=A0 =C2=A0 =C2=A0$clonedProxy =3D clone $myProxy;
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->initialize($myProxy);
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$myProxy->someProp++;
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0var_dump($clonedProxy->someProp);
>>
>> The clone was created before `someProp` was modified, but it outpu= ts the
>> value after modification!
>>
>> Also: What happens if the cloned proxy is initialized *before* the=
>> original proxy? There is no real object to clone.
>>
>> I believe the correct behavior would be: Just clone the proxy and = keep
>> the same initializer. Then both proxies are actually fully indepen= dent
>> after cloning, as I would expect from the clone operation.
>>
>
> That's basically what we do and what we describe in the RFC, just = with the
> added lazy-clone operation on the instance returned by the initializer= .
>

This means that if I would return a completely new object within the
initializer then for a cloned proxy the new object would immediately be cloned and the original object be destructed, yes?
That's correct. This preserves the lifecycle of cloned obje= cts: __clone() has to be called when creating clones if the method exists.<= br>

=C2=A0
Frankly, thinking about this cloning behavior gives me a headache,
because it quickly leads to very weird semantics. Consider the following example:

=C2=A0 =C2=A0 =C2=A0 $predefinedObject =3D new SomeObj();
=C2=A0 =C2=A0 =C2=A0 $initializer =3D function () use ($predefinedObject) {=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return $predefinedObject;
=C2=A0 =C2=A0 =C2=A0 };
=C2=A0 =C2=A0 =C2=A0 $myProxy =3D $r->newLazyProxy($initializer);
=C2=A0 =C2=A0 =C2=A0 $otherProxy =3D $r->newLazyProxy($initializer);
=C2=A0 =C2=A0 =C2=A0 $clonedProxy =3D clone $myProxy;
=C2=A0 =C2=A0 =C2=A0 $r->initialize($myProxy);
=C2=A0 =C2=A0 =C2=A0 $r->initialize($otherProxy);
=C2=A0 =C2=A0 =C2=A0 $r->initialize($clonedProxy);

To my understanding both $myProxy and $otherProxy would share the
$predefinedObject as the real instance and $clonedProxy would have a
clone of the $predefinedObject at the time of the initialization as its real instance?

Correct. The sharing is = not specifically related to cloning. But when cloning happens, the expected= behavior is well defined: we should have separate states.
=C2=A0
To me this sounds like cloning an uninitialized proxy would need to
trigger an initialization to result in semantics that do not violate the principle of least astonishment.

Forcin= g an initialization when cloning would be unexpected. E.g. in Doctrine, whe= n you clone an uninitialized entity, you don't trigger a database round= trip. Instead, you create a new object that still references the original s= tate internally, but under a different object identity. This cloning behavi= or is the one we've had for more than 10 years and I think it's als= o the least astonishing one - at least if we consider this example as a rea= l world trial of this principle.

=C2=A0
I would assume that cloning a proxy is something that rarely happens,
because my understanding is that proxies are most useful for service
objects, whereas ghost objects would be used for entities / value
objects, so this should not be too much of a problem.
=
I share this opinion. Then, since the difference between gho= st vs proxy should be internal from a consumer PoV, defining an observable = behavior for ghost objects also defines the behavior for proxy ones.

=C2=A0
>> 2.
>>
>>=C2=A0 =C2=A0> Properties are not initialized to their default v= alue yet (they are
>> initialized before calling the initializer).
>>
>> I see that you removed the bit about this being not observable. Wh= at is
>> the reason that you removed that? One possible reason that comes t= o my
>> mind is a default value that refers to a non-existing constant. It= would
>> be observable because the initialization emits an error. Are there= any
>> other reasons?
>>
>
> That's because this is observable using e.g. (array) or var_dump.<= br> >

I see. Perhaps add a short sentence with the reasoning. Something like:

Properties are not initialized to their default value yet (they are
initialized before calling the initializer). As an example, this has an impact on the behavior of an (array) cast on uninitialized objects and
also when the default value is based on a constant that is not yet
defined when creating the lazy object, but will be defined at the point of initialization.

Thanks for the propo= sal. I've added it to the RFC.

Cheers,
Nicolas
--00000000000009cd1c061d44f303--