Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124423 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 0833A1A00B7 for ; Mon, 15 Jul 2024 07:25:27 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1721028415; bh=sgC+9fctVk8YiCiBrjTopFgDw/VZIjjfRcFmQQseD64=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=enQEuJVXBAMlEVFV6y8aziBhP6Dw3aBEHw+3QJCY86WCWDqMn4Vx6kFDoueOCK2Ff zmIzT7yAHGyHfKWMEslDlJVvvYCJXzBnBIfmnyg0TUQp2mIjVgllU1ZCkyFkCr/uSq R+LtE0pvheIM+rT5AwQiSWeOpgBqc6hFA7buu7r4WNb4uoV6lrNpXR2DOKtZNw5lrA LG3WWCrTYHN6pKxCbyRrWSANj1vRVvPKFpazqGS7T7N/GqJG9CAWN0ZXjvUafIRAbd PKIxojt8w4oeYJBrTyoKDj73NChoyFyqhAEvDDCbBLKQ8t2XIdSs9TB/Vlg8X4F7+J nVCLmbB6pqxQw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 92F95180673 for ; Mon, 15 Jul 2024 07:26:53 +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-f52.google.com (mail-lf1-f52.google.com [209.85.167.52]) (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 07:26:53 +0000 (UTC) Received: by mail-lf1-f52.google.com with SMTP id 2adb3069b0e04-52e9a550e9fso4859758e87.0 for ; Mon, 15 Jul 2024 00:25:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1721028322; x=1721633122; 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=b6K4s7S7RGPiBYfTljXRYI+K6Kibaa3kG1XPr/IFfLk=; b=k0KcUvpYlFOAIaMU7q0QUvrJGPbBmb0Nf4H4Zd/deSXYDDawqjvZJWDszGe3Ni08d4 4WWJnJ0+H/eKkF9bLJ7tmR0FHRZkc6jHW/0YXBTUzbQQZQq+1D63apunCuhB0C7jUjIr 8uvG4jT7KOVjQOYYvRutjTQVYhTZQuNAC9K6RHgsL6T3upeiHVOF2UclIL+1H5ufWg0J 2NyNqpRFU7pfvedeJ20s+Zw30wUfWL9w1S4gWhqyKgIdxDvYXLZQtuuNYKStsm+IS3Sa QV5hcalcQ1Madl+jd78H37/v8Uy+EFp3YYbmTOPP9IhHTXoUX5su1rNnaj9e+qiqLG1x /I9A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1721028322; x=1721633122; 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=b6K4s7S7RGPiBYfTljXRYI+K6Kibaa3kG1XPr/IFfLk=; b=DTNJsWXIeqNnbEAo8UWeu4mg31aBzqcMy1OqzMi66SKCYwb+ut+aYFl8ocvUwqrNJn pvouBFW4kcr4SmBMYwuTp9l/Rn46qXgskV1CvKMHP4dvfuTZHTYTvC/tf4rwvqwXNDdV iF7eZLG1SJYAXL+uzof+t1Ib6sF99Q9TZnBpt/i05tEc/Ii7d877lCu44xI4OwzuSC1G z+RFga6+KP9OUHd13rrDRrnusuh0T7aM9hBjHWvUxyohJWaPRVneda8qRddIsE8EVDfp neGSmsAzMMw71N/rY+TDwQ/caU8Yu40NqdH1Bla/dtw5Tmg4i5Qgt5+2qbr92mPiEwiY x+sA== X-Forwarded-Encrypted: i=1; AJvYcCXFiGMhsET6qhLwJqadjtlV71u+I6SfwP90KqgDtdWjSd5Z1n7/AuVwxnJuZzpPw50ekajhcxgFI0gf1VPO4wIYlMbG5emN+Q== X-Gm-Message-State: AOJu0YyTI8IM+x+ziv6KWjesAjhjMpJP75ldg0vx2ZkOTQSH7paSSFx/ 9kj3sTyjqFg3LU3xk4HrPQPGOyJSmAQ7vKL2D60EfBGukyaYyl28X77ea6Bgb8Rt69dh4Pmu8dM F5UrcPEZvhodUCmbHuge2inxqq3A= X-Google-Smtp-Source: AGHT+IFnbWfzvhcvRjrOsTkOolH4kLb5KSaxsKJdlDPmK3z7gV/LeTQFMO9isSxYM+DNk/SyOqVkwIDnr7Jj0U0CR14= X-Received: by 2002:a05:6512:1246:b0:52e:9376:83aa with SMTP id 2adb3069b0e04-52ecb6359c3mr2705189e87.19.1721028321856; Mon, 15 Jul 2024 00:25:21 -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> <07e065f2-8f64-4bad-9a98-51f4eaf63ddb@app.fastmail.com> In-Reply-To: <07e065f2-8f64-4bad-9a98-51f4eaf63ddb@app.fastmail.com> Date: Mon, 15 Jul 2024 09:25:09 +0200 Message-ID: Subject: Re: [PHP-DEV] [RFC] Lazy Objects To: Rob Landers Cc: =?UTF-8?Q?Benjamin_Au=C3=9Fenhofer?= , =?UTF-8?Q?Tim_D=C3=BCsterhus?= , PHP Internals List Content-Type: multipart/alternative; boundary="000000000000c5d474061d4421aa" From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --000000000000c5d474061d4421aa Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Le ven. 12 juil. 2024 =C3=A0 08:00, Rob Landers a =C3= =A9crit : > > > On Fri, Jul 12, 2024, at 01:40, Benjamin Au=C3=9Fenhofer wrote: > > > > Am 11.07.2024, 20:31:44 schrieb Tim D=C3=BCsterhus : > > 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 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 would > > 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 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 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. > > 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. > > > I agree with Tim here, the Doctrine ORM EntityRepository plus Symfony > Service Entity Repository extension are not a necessary real world case > that would require this RFC to include a way for classes to make > themselves lazy. > > I took the liberty at rewriting the code of DefaultRepositoryFactory > (Doctrine code itself) and ContainerRepositoryFactory in a way to make th= e > repositories lazy without needing resetAsLazy, just > $reflector->createLazyProxy. In case of the second the > LazyServiceEntityRepository class could be deleted. > > https://gist.github.com/beberlei/80d7a3219b6a2a392956af18e613f86a > > Please let me know if this is not how it works or can work or if my > reasoning is flawed. > > Unless you have no way of getting to the =E2=80=9Enew $object=E2=80=9C in= the code, there > is always a way to just use newLazy*. And when a library does not expose > new $object to you to override, then that is an architectural choice (and > maybe flaw that you have to accept). > > I still think not having the reset* methods would greatly simplify this > RFC and would allow to force more constraints, have less footguns. > > For example we could simplify the API of newLazyProxy to not receive a > $factory that can arbitrarily create and get objects from somewhere, but > also initializer and always force the lazy object to be an instance creat= ed > by newInstanceWithoutConstructor. > > You said in a previous mail about reset*() > > From a technical pov, this is just a different flavor of the same code > infrastructure, so this is pretty aligned with the rest of the proposed > API. > > > We are not specifically considering the technical POV, but even more > importantly the user facing API. And this just adds to the surface of the > API a lot of things that are pushing only a 1-5% edge case. > > > > I have one question regarding the updated initialization sequence. The > > > RFC writes: > > > > > >> Properties that are declared on the real instance are uninitialized on > > > the proxy instance (including overlapping properties used with > > > ReflectionProperty::skipLazyInitialization() or > > > setRawValueWithoutLazyInitialization()) to synchronize the state shared > 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. > > --- > > > > My understanding is that the proxy will > > > always forward the property access, so there effectively is no state on > > > the proxy?! > > > > It follows that more properties can exist on the proxy itself (declared b= y > > 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: we > > > now > > >> postpone calling $real->__clone to the moment where the proxy clone is > > >> initialized. > > > > > > Do I understand it correctly that the initializer of the cloned proxy i= s > > > 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 enou= gh > > (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. > > > > ? 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 th= e > > > 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 th= e > > 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? > > 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? > > 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. > > 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. > > > 2. > > > > > > > Properties are not initialized to their default value yet (they are > > > initialized before calling the initializer). > > > > > > I see that you removed the bit about this being not observable. What is > > > 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 woul= d > > > 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. > > Best regards > Tim D=C3=BCsterhus > > > For what it=E2=80=99s worth, I see =E2=80=9CresetAsLazy()=E2=80=9D being = most useful for unit > testing libraries that build proxies. While this feature will remove most > of the tricky nuances around proxies, it doesn=E2=80=99t make it any easi= er in > generating the code for them, so that has to be tested. Being able to wri= te > a test like this (abbreviated): > > $realObj =3D new $foo() > $proxy =3D clone $realObj; > makeTestProxy($proxy); // resets as lazy with initializer > assert($realObj =3D=3D $proxy); > > Is really simple. Without a reset method, this isn=E2=80=99t straightforw= ard. > Testing is actually a good domain where resetting lazy objects might open interesting use cases. This reminded me about zenstruck/foundry, which leverages the LazyProxyTrait to provide refreshable fixture objects and provides nice DX thanks to this capability. --000000000000c5d474061d4421aa Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


=
Le=C2=A0ven. 12 juil. 2024 =C3=A0=C2= =A008:00, Rob Landers <rob@bottled.codes> a =C3=A9crit=C2=A0:


On Fri, Jul = 12, 2024, at 01:40, Benjamin Au=C3=9Fenhofer wrote:


Am 11.07.2024, 20:31:44 schrie= b Tim D=C3=BCsterhus <tim@bastelstu.be>:
Hi

On 7/11/24 10:32, Nico= las Grekas wrote:
> Many things are a= lready possible in userland. That does not always mean
> that the cost-benefit ratio is appropriate for i= nclusion in core. I get
> behi= nd 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
<= blockquote type=3D"cite">> that makes itself lazy in its own constructor= : I have not yet seen a
> real= -world example.
>

Ke= eping this capability for userland is not an option for me as it would
<= /blockquote>
mostly defeat my goal, which is to ge= t rid of any userland code on this
topic (and is achieved by the RFC).

Here is a real-world exampl= e:
https://github.com/doctrine/DoctrineBundle/blob/= 2.12.x/src/Repository/LazyServiceEntityRepository.php
<= blockquote type=3D"cite">
This cl= ass currently uses a poor-man's implementation of lazy objects and
<= /blockquote>
would greatly benefit from resetAsLaz= yGhost().

<= br>
Sorry, I was probably a little unclear with my question. I wa= s not
specifically asking if anyone did that, because I am f= airly sure that
everything possible has been done before.

I was interested in learning why I would want to= promote a
"LazyServiceEntityRepository" instead o= f the user of my library just
making the "ServiceEntity= Repository" 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 m= y 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 you= r goal
of getting rid of userland code on this topic.

To me this is what the language evolution should do:= Enable users to do
things that previously needed to be prov= ided by userland libraries,
because they were complicated an= d fragile, not enabling userland
libraries to simplify thing= s that they should not need to provide in the
first place be= cause the language already provides it.
<= div>
I agree with Tim here, the Doctrine ORM EntityRepository= plus Symfony Service Entity Repository extension are not a necessary real = world case that would require this RFC =C2=A0to include a way for classes t= o make themselves lazy.

I took the liberty at rewriting the code of DefaultRepositoryFacto= ry (Doctrine code itself) and ContainerRepositoryFactory in a way to make t= he repositories lazy without needing resetAsLazy, just $reflector->creat= eLazyProxy. In case of the second the LazyServiceEntityRepository class cou= ld be deleted.


Please let me= know if this is not how it works or can work or if my reasoning is flawed.=

Unless you have no wa= y of getting to the =E2=80=9Enew $object=E2=80=9C in the code, there is alw= ays a way to just use newLazy*. And when a library does not expose new $obj= ect to you to override, then that is an architectural choice (and maybe fla= w that you have to accept).

I still think not having the reset* methods would greatly simplify th= is RFC and would allow to force more constraints, have less footguns.=C2=A0=

For example we could = simplify the API of newLazyProxy to not receive a $factory that can arbitra= rily create and get objects from somewhere, but also initializer and always= force the lazy object to be an instance created by newInstanceWithoutConst= ructor.

You said in a = previous mail about reset*()

From a technical pov, this = is just=C2=A0=C2=A0a different flav= or of the same code infrastructure, so this is pretty<= span style=3D"background-color:rgb(249,250,251)">= =C2=A0=C2=A0aligned with the rest of the proposed API.<= /span>

We are not s= pecifically considering the technical POV, but even more importantly the us= er facing API. And this just adds to the surface of the API a lot of things= that are pushing only a 1-5% edge case.


> I have one question regardin= g the updated initialization sequence. The
> RFC writes:
>
>> Properties that are decla= red on the real instance are uninitialized on
> the proxy instance (including overlapping properties used= with
> ReflectionProperty::sk= ipLazyInitialization() or
> se= tRawValueWithoutLazyInitialization()) to synchronize the state shared by
> both instances.
>
= > I do not understand this. Specifically I do not understand the "t= o
> synchronize the state"= ; bit.


We reworded thi= s sentence a bit. Clearer?

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

---

For every property on that exists on the real instanc= e, the property on
the proxy instance effectively [1] is rep= laced by a property hook like
the following:
<= br>
=C2=A0=C2=A0=C2=A0=C2=A0public PropertyType $propertyName {<= br>
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0get {
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0return $this->realInstance->propertyName;
=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=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=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$th= is->realInstance->propertyName =3D $value;
=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0}
=C2=A0=C2=A0=C2=A0= =C2=A0}

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

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

---


<= blockquote type=3D"cite">> My understanding is that the proxy will
> always forward the property acces= s, so there effectively is no state on
> 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 u= serland implementation and
> i= ndeed,
>> we keep the wrapp= er 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 w= hen an uninitialized proxy is cloned: we
> now
>> postp= one calling $real->__clone to the moment where the proxy clone is
>> initialized.
<= blockquote type=3D"cite">>
>= ; Do I understand it correctly that the initializer of the cloned proxy is<= br>
> effectively replaced by the = following:
>
<= blockquote type=3D"cite">> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0function = (object $clonedProxy) use ($originalProxy) {
> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0return clone $originalProxy->getRealObject();
> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0}
>

Nope, that's not what we de= scribe in the RFC so I hope you can read it
again and get where you were confused and tell us if we're n= ot clear enough
(to me we are :) = )

The "cloning of the real instanc= e" 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 $c= lonedProxy, we
clone the returned= instance, so that even if the initializer returns a
shared instance, we don't share anything with the $= originalProxy.

<= div>
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=C2=A0=C2=A0$predefinedObjec= t =3D new SomeObj();
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$myProxy = =3D $r->newLazyProxy(function () use ($predefinedObject) {
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return $predefinedO= bject;
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0});
=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0$clonedProxy =3D clone $myProxy;
= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$r->initialize($myProxy);
= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$r->initialize($clonedProxy);

It didn't even occur to me that one would be able to r= eturn a
pre-existing object: I assume that simply reusing th= e initializer would
create a separate object and that would = be sufficient to ensure that the
cloned instance would be in= dependent.


> ? Then I believe this is unsound. Conside= r the following:
>
> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$my= Proxy =3D $r->newLazyProxy(...);
> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$clonedProxy =3D clone $myProxy= ;
> =C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0$r->initialize($myProxy);
> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$myProxy->someProp++;=
> =C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0var_dump($clonedProxy->someProp);
>
> The clone = was created before `someProp` was modified, but it outputs the
> value after modification!
>
= > Also: What happens if the cloned proxy is initialized *before* the
=
> original proxy? There is no rea= l 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 independen= t
> 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-clo= ne operation on the instance returned by the initializer.
<= blockquote type=3D"cite">

This means th= at if I would return a completely new object within the
init= ializer then for a cloned proxy the new object would immediately be
cloned and the original object be destructed, yes?
Frankly, thinking about this cloning behavior gives me a heada= che,
because it quickly leads to very weird semantics. Consi= der the following
example:

=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0$predefinedObject =3D new SomeObj();
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$initializer =3D function () use ($predef= inedObject) {
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0return $predefinedObject;
=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0};
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$myProxy =3D $r->n= ewLazyProxy($initializer);
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$ot= herProxy =3D $r->newLazyProxy($initializer);
=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0$clonedProxy =3D clone $myProxy;
=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0$r->initialize($myProxy);
=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0$r->initialize($otherProxy);
=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0$r->initialize($clonedProxy);

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

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.
=
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 u= sed for entities / value
objects, so this should not be too = much of a problem.

> 2= .
>
> =C2=A0=C2=A0> Properties are not initialized to the= ir default value yet (they are
&g= t; initialized before calling the initializer).
>
> I see th= at you removed the bit about this being not observable. What is
> the reason that you removed that? One p= ossible reason that comes to my
&= gt; mind is a default value that refers to a non-existing constant. It woul= d
> be observable because the = initialization emits an error. Are there any
> other reasons?
&= gt;

That's because this is observable using e.g. (array) or v= ar_dump.

I see. Perhaps add a short sentence with the reasoning. Someth= ing like:

Properties are not initialized to th= eir default value yet (they are
initialized before calling t= he initializer). As an example, this has an
impact on the be= havior 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 <= br>
of initialization.

Best regards<= br>
Tim D=C3=BCsterhus

For what it=E2=80=99s worth, I see =E2= =80=9CresetAsLazy()=E2=80=9D being most useful for unit testing libraries t= hat build proxies. While this feature will remove most of the tricky nuance= s around proxies, it doesn=E2=80=99t make it any easier in generating the c= ode for them, so that has to be tested. Being able to write a test like thi= s (abbreviated):

$realObj =3D new $foo()
$proxy =3D clone $realObj;
makeTestProxy($proxy); //= resets as lazy with initializer
assert($realObj =3D=3D $prox= y);

Is really simple. Without a reset method, = this isn=E2=80=99t straightforward.=C2=A0

Testing is actually a good domain where resetting lazy obj= ects might open interesting use cases.
This reminded me about zen= struck/foundry, which leverages the LazyProxyTrait to provide refreshable fixture objects and provides nice DX thanks to this = capability.

--000000000000c5d474061d4421aa--