Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124409 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 9B4E51A00B7 for ; Fri, 12 Jul 2024 12:42:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1720788262; bh=oZW7zev575QqULuhpTeU3ey0BOCT/thCCGCDFBAimiE=; h=In-Reply-To:References:Date:From:To:Cc:Subject:From; b=acBnkKsOwc3AhhLy4rcDcLMEjS/x25IVBlyJ2cj0BTQnDrah/6fVHIGW9Uhl5PdCF 1eUvBq1RxDCx1LOgP8G4T1ABAPaXC/EwDu+fwrIMnC5xBdZ9zpVwWb//ez2SAdajF9 d/0nkx/UYvMO0Ay3gg5Jqy8w4UmjXMvNMUTmRwPxmNwJ8+mQwucS770uY4YcevyONM y6ka6R6lxLd2Ryo4AkAJbJHtRFIXREN6pkaeaFqQODPlYhxgNa2FPkrM2+pv4u3oeL STKu0VSXCtJWWjRa4rF8F5KcpUErbSfnWk273P4aABdTXkniEqGwj/nPKqQeJtblih uvRwZzpHHR8XQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id D1C00180578 for ; Fri, 12 Jul 2024 12:44:20 +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 fhigh1-smtp.messagingengine.com (fhigh1-smtp.messagingengine.com [103.168.172.152]) (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 12:44:20 +0000 (UTC) Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailfhigh.nyi.internal (Postfix) with ESMTP id B306B1141A66; Fri, 12 Jul 2024 08:42:52 -0400 (EDT) Received: from imap49 ([10.202.2.99]) by compute1.internal (MEProxy); Fri, 12 Jul 2024 08:42:52 -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=1720788172; x= 1720874572; bh=DPzvJH82KU0K9Xh/kgKFpLeqKrdO6lUjSSxY3/uVL9k=; b=N /5rvX41pYtQ03u9XFj4sEHRhOVM/Karn2otq8rwDo4vG3PwvLIOI1L8JB4gwSmAo ByEYgzoZboEa763ADhHVOdYc7Vku4h5BYn3HP1Zm/d8bXI2yen4cMiZMqssn8BV1 AgVIyzAv5a90T7o4v7lNp6WRXUMxVJpqHZB1G0SE0nC9vPu0lpfxRIxEU6yI7Axr 1juEaZsX6bU09WaXWGwo8xKRkn9Q1p9h4kkVSTLSDg9ENVWdfo81HOotL+azsMZz rJd4nhdcNEl3yYf0T4g2nXteRloKT+/tANQZlyGaJaxu9Jv5CwOY1qs1NfjjydEx FScQL0Suh9hSyyXXFmk7Q== 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=1720788172; x=1720874572; bh=DPzvJH82KU0K9Xh/kgKFpLeqKrdO 6lUjSSxY3/uVL9k=; b=adPNsRjdLskMraQRgRvA86kjwH2CvFTqjRtfoAGJnZSY BTn658fZ5764aP7Nimn03UEHK/yIK58doLxAix7dO7nrScz9xXi1su0w+tDPX5l6 EyyIeR1fvIC122ya8m8+bO4Z85olVV57idCsX4ebtj7wO7gHRu1g8JvDASMLLrx1 PBDmIrLeURUa06g32wxTG6I02kiLTjNclc2TpU7GCm+ZImrWWDUhD9nEbYKMBJSm 0E2THXzBpZmh5qUmFveQcEdnI1FJkAi9jHO0UGe5KfGx0GN8QnOiEIcUs5n7LxjD QTKo+oYsCF4tLbqD82FO6Ljy3ey6a7TY89C6lvQhng== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeftddrfeeigdehtdcutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenuc fjughrpefofgggkfgjfhffhffvvefutgesrgdtreerreerjeenucfhrhhomhepfdftohgs ucfnrghnuggvrhhsfdcuoehrohgssegsohhtthhlvggurdgtohguvghsqeenucggtffrrg htthgvrhhnpedvkeeltefhvdffudevuedttddutdeltefguefgjeefheeijedvgeejueeu feevgfenucffohhmrghinhepghhithhhuhgsrdgtohhmnecuvehluhhsthgvrhfuihiivg eptdenucfrrghrrghmpehmrghilhhfrhhomheprhhosgessghothhtlhgvugdrtghouggv sh X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 30ABD15A0092; Fri, 12 Jul 2024 08:42:51 -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: <0d63ac33-cbdb-4bdf-b86b-0523ae13be57@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> <07e065f2-8f64-4bad-9a98-51f4eaf63ddb@app.fastmail.com> Date: Fri, 12 Jul 2024 14:42:29 +0200 To: =?UTF-8?Q?Benjamin_Au=C3=9Fenhofer?= Cc: "PHP Internals List" , "Nicolas Grekas" , =?UTF-8?Q?Tim_D=C3=BCsterhus?= Subject: Re: [PHP-DEV] [RFC] Lazy Objects Content-Type: multipart/alternative; boundary=7ec8bd05c2054acbb26aa0994a3aa9e0 From: rob@bottled.codes ("Rob Landers") --7ec8bd05c2054acbb26aa0994a3aa9e0 Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable On Fri, Jul 12, 2024, at 09:52, Benjamin Au=C3=9Fenhofer wrote: >=20 >=20 > Am 12.07.2024, 08:00:18 schrieb Rob Landers : >>=20 >>=20 >>=20 >> 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 alwa= ys 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 Strat= egies=E2=80=9D section, >>>>> > but I'm afraid I still can't wrap my head why I would want an ob= ject >>>>> > that makes itself lazy in its own constructor: I have not yet se= en a >>>>> > real-world example. >>>>> > >>>>>=20 >>>>> 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). >>>>>=20 >>>>> Here is a real-world example: >>>>> https://github.com/doctrine/DoctrineBundle/blob/2.12.x/src/Reposit= ory/LazyServiceEntityRepository.php >>>>>=20 >>>>> This class currently uses a poor-man's implementation of lazy obje= cts and >>>>> would greatly benefit from resetAsLazyGhost(). >>>>>=20 >>>>=20 >>>> Sorry, I was probably a little unclear with my question. I was not >>>> specifically asking if anyone did that, because I am fairly sure th= at >>>> everything possible has been done before. >>>>=20 >>>> 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. >>>>=20 >>>> I understand that historically making the "ServiceEntityRepository"= lazy >>>> yourself would have been very complicated, but the new RFC makes th= is >>>> super easy. >>>>=20 >>>> 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 g= oal >>>> of getting rid of userland code on this topic. >>>>=20 >>>> To me this is what the language evolution should do: Enable users t= o 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 i= n the >>>> first place because the language already provides it. >>>=20 >>> I agree with Tim here, the Doctrine ORM EntityRepository plus Symfon= y Service Entity Repository extension are not a necessary real world cas= e that would require this RFC to include a way for classes to make them= selves lazy. >>>=20 >>> I took the liberty at rewriting the code of DefaultRepositoryFactory= (Doctrine code itself) and ContainerRepositoryFactory in a way to make = the repositories lazy without needing resetAsLazy, just $reflector->crea= teLazyProxy. In case of the second the LazyServiceEntityRepository class= could 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 = reasoning 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 t= his 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, = but also initializer and always force the lazy object to be an instance = created 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 = code infrastructure, so this is pretty aligned with the rest of the pro= posed API. >>>=20 >>> We are not specifically considering the technical POV, but even more= importantly the user facing API. And this just adds to the surface of t= he API a lot of things that are pushing only a 1-5% edge case. >>>=20 >>>>=20 >>>>> > I have one question regarding the updated initialization sequenc= e. The >>>>> > RFC writes: >>>>> > >>>>> >> Properties that are declared on the real instance are uninitial= ized 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. >>>>>=20 >>>>>=20 >>>>> We reworded this sentence a bit. Clearer? >>>>=20 >>>> Yes, I think it is clearer. Let me try to rephrase this differently= to >>>> see if my understanding is correct: >>>>=20 >>>> --- >>>>=20 >>>> For every property on that exists on the real instance, the propert= y on >>>> the proxy instance effectively [1] is replaced by a property hook l= ike >>>> 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 >>>> calling the destructor if it was the last reference), as if `unset(= )` >>>> was called on the property. >>>>=20 >>>> [1] No actual property hook will be created and the `realInstance` >>>> property does not actually exist, but the semantics behave as if su= ch a >>>> hook would be applied. >>>>=20 >>>> --- >>>>=20 >>>>=20 >>>>> > My understanding is that the proxy will >>>>> > always forward the property access, so there effectively is no s= tate on >>>>> > the proxy?! >>>>>=20 >>>>>=20 >>>>> It follows that more properties can exist on the proxy itself (dec= lared 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 no= t 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 clo= ned: we >>>>> > now >>>>> >> postpone calling $real->__clone to the moment where the proxy c= lone is >>>>> >> 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(); >>>>> > } >>>>> > >>>>>=20 >>>>> 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 :) ) >>>>=20 >>>> The "cloning of the real instance" bit is what lead me to this >>>> 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 retur= ns 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 >>>> creating a fresh object within the initializer? >>>>=20 >>>> $predefinedObject =3D new SomeObj(); >>>> $myProxy =3D $r->newLazyProxy(function () use ($predefinedObje= ct) { >>>> 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 >>>> pre-existing object: I assume that simply reusing the initializer w= ould >>>> create a separate object and that would be sufficient to ensure tha= t the >>>> 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 out= puts the >>>>> > value after modification! >>>>> > >>>>> > Also: What happens if the cloned proxy is initialized *before* t= he >>>>> > original proxy? There is no real object to clone. >>>>> > >>>>> > I believe the correct behavior would be: Just clone the proxy an= d keep >>>>> > the same initializer. Then both proxies are actually fully indep= endent >>>>> > 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 = with the >>>>> added lazy-clone operation on the instance returned by the initial= izer. >>>>>=20 >>>>=20 >>>> This means that if I would return a completely new object within the >>>> initializer then for a cloned proxy the new object would immediatel= y be >>>> cloned and the original object be destructed, yes? >>>>=20 >>>> Frankly, thinking about this cloning behavior gives me a headache, >>>> because it quickly leads to very weird semantics. Consider the foll= owing >>>> 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 >>>> $predefinedObject as the real instance and $clonedProxy would have a >>>> clone of the $predefinedObject at the time of the initialization as= its >>>> real instance? >>>>=20 >>>> To me this sounds like cloning an uninitialized proxy would need to >>>> trigger an initialization to result in semantics that do not violat= e the >>>> principle of least astonishment. >>>>=20 >>>> I would assume that cloning a proxy is something that rarely happen= s, >>>> 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. >>>>=20 >>>>> > 2. >>>>> > >>>>> > > Properties are not initialized to their default value yet (t= hey 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 would >>>>> > be observable because the initialization emits an error. Are the= re 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 l= ike: >>>>=20 >>>> Properties are not initialized to their default value yet (they are >>>> initialized before calling the initializer). As an example, this ha= s 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 p= oint >>>> of initialization. >>>>=20 >>>> Best regards >>>> Tim D=C3=BCsterhus >>=20 >> For what it=E2=80=99s worth, I see =E2=80=9CresetAsLazy()=E2=80=9D be= ing most useful for unit testing libraries that build proxies. While thi= s feature will remove most of the tricky nuances around proxies, it does= n=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): >>=20 >> $realObj =3D new $foo() >> $proxy =3D clone $realObj; >> makeTestProxy($proxy); // resets as lazy with initializer >> assert($realObj =3D=3D $proxy); >>=20 >> Is really simple. Without a reset method, this isn=E2=80=99t straight= forward.=20 >=20 > I don=E2=80=99t think this RFC can replace any logic from mock testing= libraries and doesn=E2=80=99t need the objects to be lazy. Maybe I am = not seeing the use case here though. I'm not talking about mocks, I'm talking about testing code generation o= f proxies. Those may or may not be used in mocks, DI containers, etc. Ve= ry often you have to have integration tests that make sure weird code is= handled and generated properly and that those proxies resolve correctly= . This is especially important when refactoring code to implement this f= eature to ensure everything still works as before and detect any regress= ions. >=20 > The code generation part of a mock library to add the assertion logic = needs to happen anyways and making them lazy to defer initialization doe= s not seem a useful thing for a test library to do from my POV. >=20 > You can already do with ReflectionClass::newInstanceWithoutConstructor= everything that is needed for building mocks. Can you proxy an abstract class? If so, newInstanceWithoutConstructor wo= n't work. =E2=80=94 Rob --7ec8bd05c2054acbb26aa0994a3aa9e0 Content-Type: text/html;charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Fri, Jul 12,= 2024, at 09:52, Benjamin Au=C3=9Fenhofer wrote:


Am 12.07.2024, 08:00:18 schrieb Rob Landers <rob@bottled.codes>:=


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


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

On 7/11/24 10:32, Nicolas Grekas wrote:
<= blockquote type=3D"cite">> Many things are already possible in userla= nd. That does not always mean
= > that the cost-benefit ratio is appropriate for inclusion in core. I= get
> behind the two examp= les in the =E2=80=9CAbout Lazy-Loading Strategies=E2=80=9D section,
<= /blockquote>
> 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 ex= ample.
>

Keepi= ng this capability for userland is not an option for me as it would
<= /blockquote>
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-w= orld example:
https://github.com/doctrine/DoctrineBundle/blo= b/2.12.x/src/Repository/LazyServiceEntityRepository.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 m= y question. I was not
specifically asking if anyone did th= at, because I am fairly sure that
everything possible has = been done before.

I was interested in learn= ing why I would want to promote a
"LazyServiceEntityReposi= tory" instead of the user of my library just
making the "S= erviceEntityRepository" 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 bas= ed 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.<= br>

To me this is what the language evolution s= hould do: Enable users to do
things that previously needed= to be provided by userland libraries,
because they were c= omplicated 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 agr= ee with Tim here, the Doctrine ORM EntityRepository plus Symfony Service= Entity Repository extension are not a necessary real world case that wo= uld require this RFC  to include a way for classes to make themselv= es lazy.
I took the liberty a= t rewriting the code of DefaultRepositoryFactory (Doctrine code itself) = and ContainerRepositoryFactory in a way to make the repositories lazy wi= thout needing resetAsLazy, just $reflector->createLazyProxy. In case = of the 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 flawed.

Unless you hav= e no way of getting to the =E2=80=9Enew $object=E2=80=9C in the code, th= ere is always a way to just use newLazy*. And when a library does not ex= pose new $object to you to override, then that is an architectural choic= e (and maybe flaw that you have to accept).

I still think not having the reset* methods would greatly simpl= ify this RFC and would allow to force more constraints, have less footgu= ns. 

For example we could simp= lify the API of newLazyProxy to not receive a $factory that can arbitrar= ily create and get objects from somewhere, but also initializer and alwa= ys force the lazy object to be an instance created by newInstanceWithout= Constructor.

<= /div>
You said in a previous= mail about reset*()

From a = technical pov, this is just  <= span style=3D"background-color:rgb(249, 250, 251);">a different= flavor of the same code infrastructure, so this is pretty=  = ; aligned with the rest of the proposed API.

We are not specifica= lly considering the technical POV, but even more importantly the user fa= cing API. And this just adds to the surface of the API a lot of things t= hat are pushing only a 1-5% edge case.


> I have one question= regarding the updated initialization sequence. The
> RFC writes:
>
>> Propertie= s that are declared on the real instance are uninitialized on
> the proxy instance (including overl= apping properties used with
&g= t; ReflectionProperty::skipLazyInitialization() or
> setRawValueWithoutLazyInitialization()) to syn= chronize the state shared by
&= gt; both instances.
>
> I do not understand this. Spec= ifically 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 co= rrect:

---

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:

&nbs= p;   public PropertyType $propertyName {
&n= bsp;       get {
 =            return= $this->realInstance->propertyName;
  &nbs= p;     }
    =     set(PropertyType $value) {
 &= nbsp;          $this-&= gt;realInstance->propertyName =3D $value;
  &= nbsp;     }
   &nb= sp;}

And value that is stored in the proper= ty will be freed (including
calling the destructor if it w= as 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 actual= ly exist, but the semantics behave as if such a
hook would= be applied.

---


> My understanding is that the= proxy will
> always forwar= d the property access, 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).


R= ight, that's mentioned in (2), so all clear.


>&g= t; That is very true. I had a look at the userland implementation and
> indeed,
>> we keep the wrapper while cloning the ba= cking instance (it's not that we
>> have the choice, the engine doesn't give us any other option= s).
>> RFC updated.
<= /blockquote>
>>
>> We also updated the behavior when an uninitial= ized proxy is cloned: we
> = now
>> postpone calling = $real->__clone to the moment where the proxy clone is
>> initialized.
>
> D= o I understand it correctly that the initializer of the cloned proxy is<= br>
> effectively replaced by t= he following:
>
>       = function (object $clonedProxy) use ($originalProxy) {
>        &= nbsp;  return clone $originalProxy->getRealObject();
>      &= nbsp;}
>

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

The "cl= oning of the real instance" bit is what lead me to this
un= derstanding.

The $orig= inalProxy is *not* shared with $clonedProxy. Instead, it's
*initializers* that are shared between clon= es.
And then, when we call tha= t shared initializer in the $clonedProxy, we
clone the returned instance, so that even if the initiali= zer 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
creati= ng a fresh object within the initializer?

&= nbsp;    $predefinedObject =3D new SomeObj();
     $myProxy =3D $r->newLazyProxy(f= unction () use ($predefinedObject) {
   &nb= sp;     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 sufficie= nt to ensure that the
cloned instance would be independent= .


> ? Then I believe this is unsound. Consider t= he following:
>
>       = $myProxy =3D $r->newLazyProxy(...);
>       $clonedProxy =3D clone $= myProxy;
>   &nbs= p;   $r->initialize($myProxy);
>       $myProxy->= ;someProp++;
>   =     var_dump($clonedProxy->someProp);
>
> The clone was created before `someProp` was modified, but it = outputs the
> value after m= odification!
>
> Also: What happens if the cloned prox= y is initialized *before* the
= > original proxy? There is no real object to clone.
<= blockquote type=3D"cite">>
= > I believe the correct behavior would be: Just clone the proxy and k= eep
> 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 t= he
added lazy-clone operation = on the instance returned by the initializer.


This means that if I= would return a completely new object within the
initializ= er 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 he= adache,
because it quickly leads to very weird semantics. = Consider the following
example:

     $predefinedObject =3D new SomeObj();
     $initializer =3D function () u= se ($predefinedObject) {
     &nb= sp;   return $predefinedObject;
  = ;   };
     $myPro= xy =3D $r->newLazyProxy($initializer);
  &nbs= p;  $otherProxy =3D $r->newLazyProxy($initializer);
     $clonedProxy =3D clone $myProxy;
     $r->initialize($myProxy);
     $r->initialize($otherProxy= );
     $r->initialize($cloned= Proxy);

To my understanding both $myProxy a= nd $otherProxy would share the
$predefinedObject as the re= al instance and $clonedProxy would have a
clone of the $pr= edefinedObject at the time of the initialization as its
re= al instance?

To me this sounds like cloning= an uninitialized proxy would need to
trigger an initializ= ation to result in semantics that do not violate the
princ= iple of least astonishment.

I would assume = that cloning a proxy is something that rarely happens,
bec= ause my understanding is that proxies are most useful for service
objects, whereas ghost objects would be used for entities / valu= e
objects, so this should not be too much of a problem.

> 2.
=
>
>   > Properties are not initialized to their default v= alue yet (they are
> initia= lized before calling the initializer).
>
> I see that yo= u 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 would
> be observable be= cause the initialization emits an error. Are there any
<= blockquote type=3D"cite">> other reasons?
>

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

I see. Perhaps add a short sentence wi= th the reasoning. Something like:

Propertie= s 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 uninitialize= d objects and
also when the default value is based on a co= nstant 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=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 d= oesn=E2=80=99t make it any easier in generating the code for them, so th= at has to be tested. Being able to write a test like this (abbreviated):=

$realObj =3D new $foo()
$pro= xy =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 straightforward. 
=

I don=E2=80=99t think this RFC can replace any logic from mock= testing libraries and doesn=E2=80=99t need the objects to be lazy. = ; Maybe I am not seeing the use case here though.

I'm not talking about mocks, I'm talking about= testing code generation of proxies. Those may or may not be used in moc= ks, DI containers, etc. Very often you have to have integration tests th= at make sure weird code is handled and generated properly and that those= proxies resolve correctly. This is especially important when refactorin= g code to implement this feature to ensure everything still works as bef= ore and detect any regressions.


The code generation part of a mock library to add the assertion lo= gic needs to happen anyways and making them lazy to defer initialization= does not seem a useful thing for a test library to do from my POV.
<= /div>

You can already do with ReflectionClass::newI= nstanceWithoutConstructor everything that is needed for building mocks.<= br>

Can you proxy an abstrac= t class? If so, newInstanceWithoutConstructor won't work.
=

=E2=80=94 Rob
--7ec8bd05c2054acbb26aa0994a3aa9e0--