Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124402 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 C74571A00B7 for ; Fri, 12 Jul 2024 06:00:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1720764129; bh=zAxWBEGswP6Qo8SNxFdAw+woG5lPufpL5V4pr2HqmJA=; h=In-Reply-To:References:Date:From:To:Cc:Subject:From; b=dsM6/TFJaN071vqRE9vm631h5ZAw2IK72xoNbs70OjxnhF6tSQdcpfC4KLdg2vJgy gnfxpd+5AUxFscQRn1Q8Yvug/Gi3SSdiS4M9q2QooPW3aE96LgoBzEQ+wRH0g5cvrA dYD9L/fB1JCINA9PRpDwDD4uaVUagkIbN2Ju8KM6oFuv/5FhJTW+sO+4Axnk0vdBsk uqzB3LCDyEGC6mnRIT0suq57Ea8UUhT0Rf/IhbSp1lGrddCF7a0a3RN8XSsfoVx79Z s9FCfhrNFbp2e3/fG4YAGdF2V1FYCeI5ch1jA08Ye4kqUIfOqOm35Cw2WCGghUJuZU 939kTyKEB/gOA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id E74A718004A for ; Fri, 12 Jul 2024 06:02: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.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fout8-smtp.messagingengine.com (fout8-smtp.messagingengine.com [103.168.172.151]) (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 ; Fri, 12 Jul 2024 06:02:07 +0000 (UTC) Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailfout.nyi.internal (Postfix) with ESMTP id EF6B6138876B; Fri, 12 Jul 2024 02:00:39 -0400 (EDT) Received: from imap49 ([10.202.2.99]) by compute1.internal (MEProxy); Fri, 12 Jul 2024 02:00:39 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:cc:content-type:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm3; t=1720764039; x= 1720850439; bh=CI/USDWujaJMQX4LVc59YOOp7/7IVeIYAMsKTtMzQKE=; b=f e32UrqZQR+dUt1s6EcouvktDBdbE6f/KJcXiJYP2Tr/wm4nnN4KmhjIq9Quc0X3d tntTBRcH5PVsQsx1kfbTsZ4/ItbxTz/SL8Ui4M+fpqYQOBvp5j6U5xw8EKPUucqS D4EJqvC/VBOrYmYN22w9RtcVdV7E95HtQjs6wkQ9ni6tMrgVhEJdrNf6x8ESao74 ZqNzDGQ5hkZ/m4koIFabHPwKoz1mxN5HufmavPFtPBmFdaWQUm+sIDsfkzy1OHyN GAlcz1jZt4wZdElKvhCUQwTkuoMonmFT2ITrZnnwrRmAP4PzHP85OWg/JdA4/kZl 1cS6kWuLH3Sh/p0/Dhwcw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm2; t=1720764039; x=1720850439; bh=CI/USDWujaJMQX4LVc59YOOp7/7I VeIYAMsKTtMzQKE=; b=cWbEfPQXPZvlS1O7vlXQOfv3CHJU5l4zt4tEvRCQwr2a X5lMIrRbyP0oxt/S0yPMMezMDejbeEVKqN1zKeL5lGNxmXjHIsRBbu14yYAs3Pmu BnsTsM059iBNl7KKnmIaHe9TF6zG1++A2sPRawq5GUwSaFG51r9H7NReCWA7USUz YQgflo5kxkls4/ZC43pZGZdMBv3DCRP5p7gAJ316xRHi1pdlkHmwiSdN0cn7/YjW 3CB0cS2ngIMn65CEKXiF/jBlf853G3NptF/nFKwmtETQWgN/rMGudgddsdDZ82t2 EFCqF/NxG5Kt7BUtBQm3Ub92nkPVQn7TC5cE5b0Ylw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeftddrfeehgddutdefucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhepofgfggfkjghffffhvfevufgtsegrtderreerreejnecuhfhrohhmpedftfho sgcunfgrnhguvghrshdfuceorhhosgessghothhtlhgvugdrtghouggvsheqnecuggftrf grthhtvghrnhepvdekleethfdvffduveeutddtuddtleetgfeugfejfeehieejvdegjeeu ueefvefgnecuffhomhgrihhnpehgihhthhhusgdrtghomhenucevlhhushhtvghrufhiii gvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegsohhtthhlvggurdgtohgu vghs X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 26A3915A0092; Fri, 12 Jul 2024 02:00:38 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.11.0-alpha0-568-g843fbadbe-fm-20240701.003-g843fbadb Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 Message-ID: <07e065f2-8f64-4bad-9a98-51f4eaf63ddb@app.fastmail.com> In-Reply-To: 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> Date: Fri, 12 Jul 2024 08:00:18 +0200 To: =?UTF-8?Q?Benjamin_Au=C3=9Fenhofer?= , =?UTF-8?Q?Tim_D=C3=BCsterhus?= Cc: "PHP Internals List" , "Nicolas Grekas" Subject: Re: [PHP-DEV] [RFC] Lazy Objects Content-Type: multipart/alternative; boundary=b5ecbc3e8d9c46f58c5af41411b4885d From: rob@bottled.codes ("Rob Landers") --b5ecbc3e8d9c46f58c5af41411b4885d Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable On Fri, Jul 12, 2024, at 01:40, Benjamin Au=C3=9Fenhofer wrote: >=20 >=20 > Am 11.07.2024, 20:31:44 schrieb Tim D=C3=BCsterhus : >> Hi >>=20 >> 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 obje= ct >>> > that makes itself lazy in its own constructor: I have not yet seen= a >>> > real-world example. >>> > >>>=20 >>> Keeping this capability for userland is not an option for me as it w= ould >>> mostly defeat my goal, which is to get rid of any userland code on t= his >>> topic (and is achieved by the RFC). >>>=20 >>> Here is a real-world example: >>> https://github.com/doctrine/DoctrineBundle/blob/2.12.x/src/Repositor= y/LazyServiceEntityRepository.php >>>=20 >>> This class currently uses a poor-man's implementation of lazy object= s and >>> would greatly benefit from resetAsLazyGhost(). >>>=20 >>=20 >> Sorry, I was probably a little unclear with my question. I was not=20 >> specifically asking if anyone did that, because I am fairly sure that=20 >> everything possible has been done before. >>=20 >> I was interested in learning why I would want to promote a=20 >> "LazyServiceEntityRepository" instead of the user of my library just=20 >> making the "ServiceEntityRepository" lazy themselves. >>=20 >> I understand that historically making the "ServiceEntityRepository" l= azy=20 >> yourself would have been very complicated, but the new RFC makes this=20 >> super easy. >>=20 >> So based on my understanding the "LazyServiceEntityRepository"=20 >> (c|sh)ould be deprecated with the reason that PHP 8.4 provides all th= e=20 >> necessary tools to do it yourself, no? That would also match your goa= l=20 >> of getting rid of userland code on this topic. >>=20 >> To me this is what the language evolution should do: Enable users to = do=20 >> things that previously needed to be provided by userland libraries,=20 >> because they were complicated and fragile, not enabling userland=20 >> libraries to simplify things that they should not need to provide in = the=20 >> first place because the language already provides it. >=20 > 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 themse= lves lazy. >=20 > 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->create= LazyProxy. In case of the second the LazyServiceEntityRepository class c= ould be deleted. >=20 > https://gist.github.com/beberlei/80d7a3219b6a2a392956af18e613f86a >=20 > Please let me know if this is not how it works or can work or if my re= asoning is flawed. >=20 > 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 lib= rary does not expose new $object to you to override, then that is an arc= hitectural choice (and maybe flaw that you have to accept). >=20 > I still think not having the reset* methods would greatly simplify thi= s RFC and would allow to force more constraints, have less footguns.=20 >=20 > For example we could simplify the API of newLazyProxy to not receive a= $factory that can arbitrarily create and get objects from somewhere, bu= t also initializer and always force the lazy object to be an instance cr= eated by newInstanceWithoutConstructor. >=20 > You said in a previous mail about reset*() >=20 >> From a technical pov, this is just a different flavor of the same co= de infrastructure, so this is pretty aligned with the rest of the propo= sed API. >=20 > We are not specifically considering the technical POV, but even more i= mportantly 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. >=20 >>=20 >>> > I have one question regarding the updated initialization sequence.= The >>> > RFC writes: >>> > >>> >> Properties that are declared on the real instance are uninitializ= ed 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 "to >>> > synchronize the state" bit. >>>=20 >>>=20 >>> We reworded this sentence a bit. Clearer? >>=20 >> Yes, I think it is clearer. Let me try to rephrase this differently t= o=20 >> see if my understanding is correct: >>=20 >> --- >>=20 >> For every property on that exists on the real instance, the property = on=20 >> the proxy instance effectively [1] is replaced by a property hook lik= e=20 >> the following: >>=20 >> public PropertyType $propertyName { >> get { >> return $this->realInstance->propertyName; >> } >> set(PropertyType $value) { >> $this->realInstance->propertyName =3D $value; >> } >> } >>=20 >> And value that is stored in the property will be freed (including=20 >> calling the destructor if it was the last reference), as if `unset()`=20 >> was called on the property. >>=20 >> [1] No actual property hook will be created and the `realInstance`=20 >> property does not actually exist, but the semantics behave as if such= a=20 >> hook would be applied. >>=20 >> --- >>=20 >>=20 >>> > My understanding is that the proxy will >>> > always forward the property access, so there effectively is no sta= te on >>> > the proxy?! >>>=20 >>>=20 >>> It follows that more properties can exist on the proxy itself (decla= red by >>> child classes of the real object that the proxy implements). >>>=20 >>=20 >> Right, that's mentioned in (2), so all clear. >>=20 >>>=20 >>> >> 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 clone= d: we >>> > now >>> >> postpone calling $real->__clone to the moment where the proxy clo= ne is >>> >> initialized. >>> > >>> > Do I understand it correctly that the initializer of the cloned pr= oxy is >>> > effectively replaced by the following: >>> > >>> > function (object $clonedProxy) use ($originalProxy) { >>> > return clone $originalProxy->getRealObject(); >>> > } >>> > >>>=20 >>> 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 :) ) >>=20 >> The "cloning of the real instance" bit is what lead me to this=20 >> understanding. >>=20 >>> 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. >>>=20 >>=20 >> Ah, so you mean if the initializer would look like this instead of=20 >> creating a fresh object within the initializer? >>=20 >> $predefinedObject =3D new SomeObj(); >> $myProxy =3D $r->newLazyProxy(function () use ($predefinedObject= ) { >> return $predefinedObject; >> }); >> $clonedProxy =3D clone $myProxy; >> $r->initialize($myProxy); >> $r->initialize($clonedProxy); >>=20 >> It didn't even occur to me that one would be able to return a=20 >> pre-existing object: I assume that simply reusing the initializer wou= ld=20 >> create a separate object and that would be sufficient to ensure that = the=20 >> cloned instance would be independent. >>=20 >>>=20 >>> > ? 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 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. >>> > >>>=20 >>> That's basically what we do and what we describe in the RFC, just wi= th the >>> added lazy-clone operation on the instance returned by the initializ= er. >>>=20 >>=20 >> This means that if I would return a completely new object within the=20 >> initializer then for a cloned proxy the new object would immediately = be=20 >> cloned and the original object be destructed, yes? >>=20 >> Frankly, thinking about this cloning behavior gives me a headache,=20 >> because it quickly leads to very weird semantics. Consider the follow= ing=20 >> example: >>=20 >> $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); >>=20 >> To my understanding both $myProxy and $otherProxy would share the=20 >> $predefinedObject as the real instance and $clonedProxy would have a=20 >> clone of the $predefinedObject at the time of the initialization as i= ts=20 >> real instance? >>=20 >> To me this sounds like cloning an uninitialized proxy would need to=20 >> trigger an initialization to result in semantics that do not violate = the=20 >> principle of least astonishment. >>=20 >> I would assume that cloning a proxy is something that rarely happens,=20 >> because my understanding is that proxies are most useful for service=20 >> objects, whereas ghost objects would be used for entities / value=20 >> objects, so this should not be too much of a problem. >>=20 >>> > 2. >>> > >>> > > Properties are not initialized to their default value yet (the= y 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? >>> > >>>=20 >>> That's because this is observable using e.g. (array) or var_dump. >>>=20 >>=20 >> I see. Perhaps add a short sentence with the reasoning. Something lik= e: >>=20 >> Properties are not initialized to their default value yet (they are=20 >> initialized before calling the initializer). As an example, this has = an=20 >> impact on the behavior of an (array) cast on uninitialized objects an= d=20 >> also when the default value is based on a constant that is not yet=20 >> defined when creating the lazy object, but will be defined at the poi= nt=20 >> of initialization. >>=20 >> 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 f= eature will remove most of the tricky nuances around proxies, it doesn=E2= =80=99t make it any easier in generating the code for them, so that has = to be tested. Being able to write 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 straightfor= ward.=20 =E2=80=94 Rob --b5ecbc3e8d9c46f58c5af41411b4885d Content-Type: text/html;charset=utf-8 Content-Transfer-Encoding: quoted-printable

=
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@bastels= tu.be>:
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
<= /blockquote>
> behind the two examples in th= e =E2=80=9CAbout Lazy-Loading Strategies=E2=80=9D section,
> but I'm afraid I still can't wrap my h= ead 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 c= apability 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
t= opic (and is achieved by the RFC).

Here is a real-world exa= mple:
https://github.com/doctrine/DoctrineBundle/blob/2.12.x= /src/Repository/LazyServiceEntityRepository.php

This cl= ass currently uses a poor-man's implementation of lazy objects and
would greatly benefit from resetAsL= azyGhost().

<= div>
Sorry, I was probably a little unclear with my questi= on. I was not
specifically asking if anyone did that, bec= ause I am fairly sure that
everything possible has been d= one before.

I was interested in learning wh= y I would want to promote a
"LazyServiceEntityRepository"= instead of the user of my library just
making the "Servi= ceEntityRepository" lazy themselves.

I unde= rstand that historically making the "ServiceEntityRepository" lazy
<= /div>
yourself would have been very complicated, but the new RFC mak= es 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 need= ed to be provided by userland libraries,
because they wer= e complicated and fragile, not enabling userland
librarie= s to simplify things that they should not need to provide in the
first place because the language already provides it.
<= /div>

I a= gree with Tim here, the Doctrine ORM EntityRepository plus Symfony Servi= ce Entity Repository extension are not a necessary real world case that = would require this RFC  to include a way for classes to make themse= lves lazy.

=
I took the liberty at re= writing the code of DefaultRepositoryFactory (Doctrine code itself) and = ContainerRepositoryFactory in a way to make the repositories lazy withou= t needing resetAsLazy, just $reflector->createLazyProxy. In case of t= he second the LazyServiceEntityRepository class could be deleted.


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

Unless you have no way of getting to t= he =E2=80=9Enew $object=E2=80=9C in the code, there is always a way to j= ust 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 y= ou have to accept).
<= br>
I still think not hav= ing the reset* methods would greatly simplify this RFC and would allow t= o force more constraints, have less footguns. 

For example we could simplify the API of newLazyProxy to not re= ceive a $factory that can arbitrarily create and get objects from somewh= ere, but also initializer and always force the lazy object to be an inst= ance created by newInstanceWithoutConstructor.

You said in a previous mail about reset*()

From a technical pov, this is just  <= span class=3D"font" style=3D"font-family:ui-sans-serif, system-ui, -appl= e-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvet= ica Neue", Arial, "Noto Sans", sans-serif, "Apple Co= lor Emoji", "Segoe UI Emoji", "Segoe UI Symbol"= , "Noto Color Emoji";">a different flavor of the same code infrastructure, so this is p= retty  aligned with the rest of the prop= osed API.

We are not specifically considering the technical POV, but even more im= portantly 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.

<= blockquote class=3D"qt-gmail_quote" style=3D"margin-top:0px;margin-right= :0px;margin-bottom:0px;margin-left:0.8ex;border-left-width:1px;border-le= ft-style:solid;border-left-color:rgb(204, 204, 204);padding-left:1ex;" t= ype=3D"cite">

> I h= ave one question regarding the updated initialization sequence. The
<= /blockquote>
> RFC writes:
<= blockquote type=3D"cite">>
= >> Properties that are declared on the real instance are uninitial= ized on
> the proxy instanc= e (including overlapping properties used with
> ReflectionProperty::skipLazyInitialization() or
=
> setRawValueWithoutLazyInitia= lization()) to synchronize the state shared by
> both instances.
>
> I do not unde= rstand this. Specifically I do not understand the "to
> synchronize the state" bit.

We reworded this sentence a bit= . Clearer?

Yes, I think it is cleare= r. Let me try to rephrase this differently to
see if my u= nderstanding is correct:

---
=
For every property on that exists on the real instance, t= he property on
the proxy instance effectively [1] is repl= aced by a property hook like
the following:

    public PropertyType $propertyNa= me {
        get = {
         &= nbsp;  return $this->realInstance->propertyName;
        }
=         set(PropertyType $value= ) {
         = ;   $this->realInstance->propertyName =3D $value;
        }
    }

And value = that is stored in the property will be freed (including
c= alling 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.

---


&= gt; My understanding is that the proxy will
> always forward the property access, so there effectiv= ely is no state on
> the pr= oxy?!


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 cl= ear.


=
>> That is very true. I had a look at th= e 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.
&g= t;>
>> We also update= d the behavior when an uninitialized proxy is cloned: we
> now
>> postpone calling $real->__clone to the moment where th= e proxy clone is
>> init= ialized.
>
=
> Do I understand it correctly that the ini= tializer of the cloned proxy is
> effectively replaced by the following:
>
> &nbs= p;     function (object $clonedProxy) use ($ori= ginalProxy) {
>   = ;        return clone $originalP= roxy->getRealObject();
>=       }
>

<= blockquote type=3D"cite">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 le= ad me to this
understanding.

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


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

     $predefine= dObject =3D new SomeObj();
     = $myProxy =3D $r->newLazyProxy(function () use ($predefinedObject) {
         retu= rn $predefinedObject;
     });
     $clonedProxy =3D clone $myPr= oxy;
     $r->initialize($myP= roxy);
     $r->initialize($c= lonedProxy);

It didn't even occur to me tha= t one would be able to return a
pre-existing object: I as= sume that simply reusing the initializer would
create a s= eparate 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->newLazyPro= xy(...);
>   &nbs= p;   $clonedProxy =3D clone $myProxy;
>       $r->i= nitialize($myProxy);
> &nbs= p;     $myProxy->someProp++;
>       var_= dump($clonedProxy->someProp);
>
> The clone was cre= ated before `someProp` was modified, but it outputs the
=
> value after modification!
>
> Also: What happens if the cloned proxy is initialized *before* th= e
> original proxy? There i= s no real object to clone.
>= ;
> I believe the correct b= ehavior would be: Just clone the proxy and keep
> the same initializer. Then both proxies are actua= lly fully independent
> aft= er 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 n= ew object within the
initializer then for a cloned proxy = the new object would immediately be
cloned and the origin= al object be destructed, yes?

Frankly, thin= king about this cloning behavior gives me a headache,
bec= ause it quickly leads to very weird semantics. Consider the following
example:

   &n= bsp; $predefinedObject =3D new SomeObj();
 &nbs= p;   $initializer =3D function () use ($predefinedObject)= {
         = return $predefinedObject;
     }= ;
     $myProxy =3D $r->newLa= zyProxy($initializer);
     $oth= erProxy =3D $r->newLazyProxy($initializer);
 &nbs= p;   $clonedProxy =3D clone $myProxy;
&nbs= p;    $r->initialize($myProxy);
&n= bsp;    $r->initialize($otherProxy);
     $r->initialize($clonedProxy);

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

To me this sounds like cloning an uniniti= alized proxy would need to
trigger an initialization to r= esult in semantics that do not violate the
principle of l= east astonishment.

I would assume that clon= ing 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
<= /div>
objects, so this should not be too much of a problem.

> 2.
>
> =   > Properties are not initialized to their default value y= et (they are
> initialized = before calling the initializer).
>
> I see that you re= moved the bit about this being not observable. What is
<= blockquote type=3D"cite">> the reason that you removed that? One poss= ible reason that comes to my
&= gt; mind is a default value that refers to a non-existing constant. It w= ould
> be observable becaus= e 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 t= he reasoning. Something like:

Properties ar= e not initialized to their default value yet (they are
in= itialized 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 con= stant that is not yet
defined when creating the lazy obje= ct, but will be defined at the point
of initialization.

Best regards
Tim D=C3=BCsterhu= s

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. Whi= le this feature will remove most of the tricky nuances around proxies, i= t doesn=E2=80=99t make it any easier in generating the code for them, so= that has to be tested. Being able to write a test like this (abbreviate= d):

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

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

=E2=80=94 Rob
--b5ecbc3e8d9c46f58c5af41411b4885d--