Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124405 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 5CAE61A00B7 for ; Fri, 12 Jul 2024 07:52:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1720770854; bh=XUVwESe7JmjN281BAiiOxqWUgr6Dcyydpg5A3KwybeM=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=SyA/ccKfgmRZ4/k0LFaTk9s+XWmnZdUIv41wq5NcNjFv7GWvMY9mP2BlAPce4GZHv DISQyvOQY8MNR06GPi7Lc5lBzaEAfRNQu3DRpxqE/Nh0GyHBAfIL7SsWLJH0H3AC3F b+JwGoAhLviAuYyhxLH7RxEmQNGSveRNdf5iIJylIwPOG2ZOmuq0CkzpZMzstzbrIf XsqweX1ia6XCG5shYaDzu3XLA6q/wblGzPocg0Y7ggKb3ybtPt4SIDs5d/wilRLyud q49q6wunquNhJFUEnypLRTmB5Px88RyCwUTvkckG6h73gDkv7zsY5rOlV71s+lebdZ Ioep30v3J6hbg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id B86131805BC for ; Fri, 12 Jul 2024 07:54:12 +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.8 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DMARC_MISSING,HTML_MESSAGE,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from mail-vs1-f53.google.com (mail-vs1-f53.google.com [209.85.217.53]) (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 07:54:12 +0000 (UTC) Received: by mail-vs1-f53.google.com with SMTP id ada2fe7eead31-48fe73d4f6cso668955137.2 for ; Fri, 12 Jul 2024 00:52:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=beberlei-de.20230601.gappssmtp.com; s=20230601; t=1720770764; x=1721375564; 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=OxczaJMktbkN4uf+Vpai7xNjRFl4Gjvxs2qHivKvr4Y=; b=lnuMd7wyZiBxnOoYdn86bk66Fw594Stdb9JTl48ZMTuoMHPZpZ9+OplG3Bvv4WLHeg ySl2b/sQpTttcXIVwoLOPHeilPE+W0GJYfzvGP15nA3jX0dpc7GyaGLEz/3Ky2+z2+Ec iC+PGpOVa1LMrCUsTneKRZ4/p0kpwLIrAABRD+0v9hNKKH07RMdkFR2ojahhb9ONnOpR kDrOikCiyF/AjAKBz7+Q5ed1AgjpZiGQQW3PzrwF8g+Fu+Uxq9EyzdLQgHPJJC06sFjs cVr8JAn3Y/4aDddyZVVniUoO1LBr9sI0JbZKkfb9GaHPiN1oQrBOZrGpkpzBvVcWRtet v98Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720770764; x=1721375564; 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=OxczaJMktbkN4uf+Vpai7xNjRFl4Gjvxs2qHivKvr4Y=; b=KhjuUeHZpVRxFYhnRJrjs1W5p9WpMqC91iGb6+0W9HrHXJBDkhzrZK5KYGKHaeBK+F 7qO0nzsD43zay+zWLbP3xAvXgeWDloP3Z/PTWc9gEW7htcZMhIFuzGqjCi0oa9Oz2Sek WMwwUEpfXCfH9YeNRIlOHxd9YUNZaL/avKWLlq7RzwmJGqPkxcrRkq64rPwfUh8BRZYZ kJkbBqJZxHQbzyUMrYPtw9WypCGPMCDUc2USmz3Am1aJGhSFvSXBjsD2pvmM5ZeBXmVa Kz1i8GVPkgP3UVCgZIpUEjw/h2zagOg5qNDi32ei7E7M+fZY9gnCkFH0/aZo8mk9RPIL +yFQ== X-Gm-Message-State: AOJu0YwmQbZhCPx926Wl5cTffrMwW9KE0kwjHPQTIIUjlE6sGBT/IoBI PSKYG8JVxqN4XCSL6SsCnz6LoFrC9+4DeGF/u2+0U7INhqU1sE9hPbPsskX0KCiiB2WAwv57e50 gP9eiEWXtiBBVNSKG6EtVTiTGuuuR2WhkD4W/Hg== X-Google-Smtp-Source: AGHT+IGELFwz3ZLZ3mPOxtGNaC0/AdVrYM1SVP7RGhEbnJnBPIrdInwOfEvImJFW9CSDIyELrOPvQhrRHz2mpp3GXlU= X-Received: by 2002:a05:6102:e14:b0:48f:c2dd:3520 with SMTP id ada2fe7eead31-49032120d0amr12326068137.11.1720770763123; Fri, 12 Jul 2024 00:52:43 -0700 (PDT) Received: from 1064022179695 named unknown by gmailapi.google.com with HTTPREST; Fri, 12 Jul 2024 09:52:42 +0200 Received: from 1064022179695 named unknown by gmailapi.google.com with HTTPREST; Fri, 12 Jul 2024 00:52:39 -0700 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 (Mimestream 1.3.6) 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: Fri, 12 Jul 2024 09:52:42 +0200 Message-ID: Subject: Re: [PHP-DEV] [RFC] Lazy Objects To: Rob Landers Cc: PHP Internals List , Nicolas Grekas , =?UTF-8?Q?Tim_D=C3=BCsterhus?= Content-Type: multipart/alternative; boundary="0000000000001387a3061d082a13" From: kontakt@beberlei.de (=?UTF-8?Q?Benjamin_Au=C3=9Fenhofer?=) --0000000000001387a3061d082a13 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Am 12.07.2024, 08:00:18 schrieb Rob Landers : > > > 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. > I don=E2=80=99t think this RFC can replace any logic from mock testing libr= aries and doesn=E2=80=99t need the objects to be lazy. Maybe I am not seeing the= use case here though. The code generation part of a mock library to add the assertion logic 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. You can already do with ReflectionClass::newInstanceWithoutConstructor everything that is needed for building mocks. The only thing a lazy proxy / ghost could reasonbly do for mocking is to allow saying what method was first called on the mock, but only when using debug_backtrace in the factory method. Maybe we could extend the proxy functionality in a follow-up RFC to allow passing a $callInterceptor callback that gets invoked on every call to the proxy. But this does not make reset* methods necessary. > =E2=80=94 Rob > --0000000000001387a3061d082a13 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


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


<= div>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 = <tim@bastelstu.be>:
=
Hi

On 7/11/24 10:32, Nicolas = Grekas wrote:
> Many things are alrea= dy possible in userland. That does not always mean
> that the cost-benefit ratio is appropriate for inclu= sion in core. I get
> behind t= he two examples in the =E2=80=9CAbout Lazy-Loading Strategies=E2=80=9D sect= ion,
> but I'm afraid I st= ill 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-wor= ld example.
>
=

Keepin= g this capability for userland is not an option for me as it would
mostly defeat my goal, which is to get ri= d of any userland code on this
to= pic (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 p= oor-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
s= pecifically 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
<= div>"LazyServiceEntityRepository" instead of the user of my libra= ry just
making the "ServiceEntityRepository" lazy = themselves.

I understand that historically mak= ing the "ServiceEntityRepository" lazy
yourself wo= uld have been very complicated, but the new RFC makes this
s= uper easy.

So based on my understanding the &q= uot;LazyServiceEntityRepository"
(c|sh)ould be deprecat= ed with the reason that PHP 8.4 provides all the
necessary t= ools 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 libra= ries,
because they were complicated and fragile, not enablin= g userland
libraries to simplify things that they should not= need to provide in the
first place because the language alr= eady provides it.

I agree with Tim here, the Doctrine ORM EntityReposit= ory plus Symfony Service Entity Repository extension are not a necessary re= al world case that would require this RFC =C2=A0to include a way for classe= s to make themselves lazy.

I took the lib= erty at rewriting the code of DefaultRepositoryFactory (Doctrine code itsel= f) 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 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 s= implify this RFC and would allow to force more constraints, have less footg= uns.=C2=A0

For example we could simplify the AP= I 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.

You said in a previous mail about reset*()

From a technical pov, this = is just=C2=A0=C2=A0a different flavor of the same = code infrastructure, so this is pretty=C2=A0=C2=A0= aligned with the rest of the proposed API.

We are not specifically considering the tech= nical POV, but even more importantly the user facing API. And this just add= s to the surface of the API a lot of things that are pushing only a 1-5% ed= ge case.


> = I have one question regarding the updated initialization sequence. The
<= /blockquote>
> RFC writes:
>
>&g= t; Properties that are declared on the real instance are uninitialized on
> the proxy instance (including= overlapping properties used with
> ReflectionProperty::skipLazyInitialization() or
> setRawValueWithoutLazyInitialization()) to synchr= onize the state shared by
> bo= th instances.
>
> I do not understand this. Specifically I d= o 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 dif= ferently to
see if my understanding is correct:

---

For every property on tha= t exists on the real instance, the property on
the proxy ins= tance effectively [1] is replaced by a property hook like
th= e following:

=C2=A0=C2=A0=C2=A0=C2=A0public P= ropertyType $propertyName {
=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->prope= rtyName;
=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(PropertyT= ype $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$this->realInstance->propertyName =3D $val= ue;
=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 t= hat is stored in the property will be freed (including
calli= ng 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`
proper= ty does not actually exist, but the semantics behave as if such a
hook would be applied.

---


> My understanding i= s 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 th= e 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 cho= ice, 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
>> initi= alized.
>
> Do I understand it correctly that the initialize= r of the cloned proxy is
> eff= ectively replaced by the following:
>
> =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 describe in the RFC so I hope you can read it
<= /blockquote>
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 $or= iginalProxy is *not* shared with $clonedProxy. Instead, it's
*initializers* that are shared between clon= es.
And then, when we call that s= hared initializer in the $clonedProxy, we
clone the returned instance, so that even if the initializer retu= rns a
shared instance, we don'= ;t share anything with the $originalProxy.


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

=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0$predefinedObject =3D new SomeObj();
=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0$myProxy =3D $r->newLazyProxy(function () use ($pre= definedObject) {
=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$clonedProxy =3D = clone $myProxy;
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$r->initial= ize($myProxy);
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$r->initiali= ze($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 s= eparate object and that would be sufficient to ensure that the
cloned instance would be independent.


> ? Then I be= lieve this is unsound. Consider the following:
>
> =C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0$myProxy =3D $r->newLazyProxy(...);
> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$= clonedProxy =3D clone $myProxy;
&= gt; =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 m= odification!
>
> Also: What happens if the cloned proxy is i= nitialized *before* the
> orig= inal 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 ar= e actually fully independent
>= after cloning, as I would expect from the clone operation.
>
<= br>
That's basically what we do a= nd what we describe in the RFC, just with the
added lazy-clone operation on the instance returned by the ini= tializer.

<= br>
This means that if I would return a completely new object wit= hin the
initializer then for a cloned proxy the new object w= ould immediately be
cloned and the original object be destru= cted, yes?

Frankly, thinking about this clonin= g behavior gives me a headache,
because it quickly leads to = very weird semantics. Consider 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 ($predefinedObject) {
=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->newLazyProxy($initializer);
=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0$otherProxy =3D $r->newLazyProxy($initializer);<= br>
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$clonedProxy =3D clone $myProx= y;
=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($otherProx= y);
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0$r->initialize($clonedP= roxy);

To my understanding both $myProxy and $= otherProxy would share the
$predefinedObject as the real ins= tance and $clonedProxy would have a
clone of the $predefined= Object at the time of the initialization as its
real instanc= e?

To me this sounds like cloning an uninitial= ized proxy would need to
trigger an initialization to result= in semantics that do not violate the
principle of least ast= onishment.

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, s= o this should not be too much of a problem.

> 2.
>
> =C2=A0=C2=A0> Properties a= re 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 observ= able. What is
> the reason tha= t you removed that? One possible reason that comes to my
> mind is a default value that refers to a non-e= xisting constant. It would
> b= e observable because the initialization emits an error. Are there any
> other reasons?
>

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


I see. Perhaps add a short sentence w= ith the reasoning. Something like:

Properties = are not initialized to their default value yet (they are
ini= tialized 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 f= or unit testing libraries that build proxies. While this feature will remov= e 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 ab= le to write a test like this (abbreviated):

$r= ealObj =3D new $foo()
$proxy =3D clone $realObj;
makeTestProxy($proxy); // resets as lazy with initializer
a= ssert($realObj =3D=3D $proxy);

Is really simpl= e. Without a reset method, this isn=E2=80=99t straightforward.=C2=A0
<= /div>

I don=E2=80=99t think this RFC can replace any log= ic from mock testing libraries and doesn=E2=80=99t need the objects to be l= azy.=C2=A0 Maybe I am not seeing the use case here though.

The code generation part of a mock library to add the assertion logic ne= eds 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.

You can already do with ReflectionClass::newInstanceWithoutConstructor e= verything that is needed for building mocks.

The only thi= ng a lazy proxy / ghost could reasonbly do for mocking is to allow saying w= hat method was first called on the mock, but only when using debug_backtrac= e in the factory method.=C2=A0
=
Maybe we could extend the = proxy functionality in a follow-up RFC to allow passing a $callInterceptor = callback that gets invoked on every call to the proxy. But this does not ma= ke reset* methods necessary.=C2=A0


=E2=80=94 Rob
--0000000000001387a3061d082a13--