Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124381 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 748B61A00B7 for ; Thu, 11 Jul 2024 08:32:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1720686858; bh=om6oAOq2y38+dEztv2/u8ilyYVVjH50oeANapJHQ3CA=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=iNPJPXGBYb3cLaCqGL3q2EniGb4pcuc6vW5fgHcjVOKeIP2DhhcEhRhVqytVJOKGY xZh16ghZlnR0PT6eMPWGL9njNC9pw+vxQK3Lc9YbJ5quRMMthFV3OBu5rJ9CLunYfn wJv1dXuEupJeJU3NJSIF3lov+CQQVyjsBZeFlBiRa+gWl2mzR4umnQGPVFUYPzmm2c 8mQkQi+2ShAqGrDitr4M55dIGySffjGSf7MamKapZ7LJDvWld4LZEsX/+zAzeMYyz0 0ULljwlFkAhBHw/kogpFRSMZz9cdhaveb8LbE4IRGW/j0D8Vsf+5aX5/SOUbWJcaRo 1k+X8dhRt+QSw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 535CE180580 for ; Thu, 11 Jul 2024 08:34:17 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from mail-lj1-f182.google.com (mail-lj1-f182.google.com [209.85.208.182]) (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 ; Thu, 11 Jul 2024 08:34:16 +0000 (UTC) Received: by mail-lj1-f182.google.com with SMTP id 38308e7fff4ca-2ee77db6f97so9518561fa.2 for ; Thu, 11 Jul 2024 01:32:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1720686769; x=1721291569; 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=37y1YJS8vbGzdxuBcoc+evYkY4CJLmt/vAxrbaJLsEk=; b=cqqJPFznrTRwTOjmhrJAiOLVfZIJ+70MAQducdlmeOVR3Ad7SISXsCuiABUL+EO2Ae IQdHHBjLVfowChcZc3NPisfwegZehKlWqeIN8Q6RNM4+JSHSWrfis0G42cqf9oyV7ces GN1rtnCF/FRtiwolY+dukBTnliPhGI0YZznNiMaHJxo8XVFPevgNQTGq3dyQ6inJ3t8s V2Ih4tH/gPxUakwSDOaPLgg96C113//pOz53+oj21q44U9533lDoXZLpGk/n1cctnb5K lV1wKjtptK74VUkAirz6JlLTLoDdCCuMwyrJJwb8HH7UHHoRtqAd7vgaQc3KA2XLPMn5 mm2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720686769; x=1721291569; 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=37y1YJS8vbGzdxuBcoc+evYkY4CJLmt/vAxrbaJLsEk=; b=kzXOIyw2mNiDvfK3nvqH8Fi096TzUqLibGpGUPbpjDE8KyBCSZSgB2cycrEJgWJPx8 VbG7duaRxUJsDjJh8JNmmJfXeqlcTkUjpXoGdaIGugOEmufYCaPG++DiQe85Y6aGgx4p 853IKoY2EOUtNIj9QWJ09GutcQ7JHjdZXm1A+LfpWkCqKyvQIXOeXMpU7kPXmrRzJEkF HLg/h1NkSSWezuEjY+2u+N4V201KsLXjPWz8LFPEQm+WLeXndwXAQxJlK6Vj51Y3lC09 O4Aw7MM63h0pnSYyOQjfuixf1GWZcHIRIZPhoaTDCANsJ91LKAWGnbPo90gipQepJY6u x/vw== X-Gm-Message-State: AOJu0YyuyNcseEEcylK+iKfBB4h1Z0yzJ+n3aM8lIxFUEQrIRptt/Ncd jxz4npLgDhn+Vkwc4OOLgVQyl8embryj9+gg1kuuroYMu8vhmpY4Lv1IHTpTSHaWkZ3frGS/Jol 5Tl/76AITOp+sHQjPvdU5t2zjX4OYpHSjcxc= X-Google-Smtp-Source: AGHT+IE9Ot3bUDoE5l8+bdvEsrmnEeCVLrWjars3GGdJnGF6HMmDb+YI+ISTyEKAoTcEnqwYqtA56PblhMdiSOj+fHA= X-Received: by 2002:a05:6512:a90:b0:52c:905b:ea5f with SMTP id 2adb3069b0e04-52eb99d656amr6222800e87.63.1720686768426; Thu, 11 Jul 2024 01:32:48 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 References: <1118bbcd-a7b4-47bf-bf35-1a36ab4628e1@bastelstu.be> <45847b93-02bf-459f-bcd2-81ba35a12c24@bastelstu.be> <46bd4098-2936-4e46-98e9-fe55118325c2@bastelstu.be> In-Reply-To: <46bd4098-2936-4e46-98e9-fe55118325c2@bastelstu.be> Date: Thu, 11 Jul 2024 10:32:36 +0200 Message-ID: Subject: Re: [PHP-DEV] [RFC] Lazy Objects To: =?UTF-8?Q?Tim_D=C3=BCsterhus?= Cc: PHP Internals List Content-Type: multipart/alternative; boundary="0000000000009a1827061cf49bdb" From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --0000000000009a1827061cf49bdb Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Le ven. 5 juil. 2024 =C3=A0 21:49, Tim D=C3=BCsterhus a = =C3=A9crit : > Hi > > On 7/2/24 16:48, Nicolas Grekas wrote: > > Thanks for the detailed feedback again, it's very helpful! > > Let me try to answer many emails at once, in chronological order: > > Note that this kind of bulk reply make it very hard for me to keep track > of mailing list threads. It breaks threading, which makes it much harder > for me to find original context of a quoted part, especially since you > did not include the author / date for the quotes. > Noted. > That said, I've taken a look at the differences since my email and also > gave the entire RFC another read. > > > don't touch `readonly` because of lazy objects: this feature is too nic= he > >> to cripple a major-major feature like `readonly`. I would suggest > deferring > >> until after the first bits of this RFC landed. > >> > > > > Following Marco's advice, we've decided to remove all the flags related > to > > the various ways to handle readonly. This also removes the secondary > vote. > > The behavior related to readonly properties is now that they are skippe= d > if > > already initialized when calling resetAsLazy* methods, throw in the > > initializer as usual, and are resettable only if the class is not final= , > as > > already allowed in userland (and as explained in the RFC). > > The 'readonly' section still mentions 'makeInstanceLazy', which likely > is a left-over from a previous version of the RFC. You should have > another look and clean up the naming there. > I found a few other outdated occurrences. They should all be updated now. > >>> There are not many reasons to do that. The only indented use-case tha= t > >>> doesn't involve an object freshly created with > >>> ->newInstanceWithoutConstructor() is to let an object manage its own > >>> laziness by making itself lazy in its constructor: > >>> > >> > >> Okay. But the RFC (and your email) does not explain why I would want d= o > >> that. It appears that much of the RFC's complexity (e.g. around readon= ly > >> properties and destructors) stems from the wish to support turning an > >> existing object into a lazy object. If there is no strong reason to > >> support that, I would suggest dropping that. It could always be added = in > >> a future PHP version. > >> > > > > This capability is needed for two reasons: 1. completeness and 2. featu= re > > parity with what can be currently done using magic methods (so that it'= s > > already used to solve real-world problems). > > 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/LazyS= erviceEntityRepository.php This class currently uses a poor-man's implementation of lazy objects and would greatly benefit from resetAsLazyGhost(). > > True, thanks for raising this point. After brainstorming with Arnaud, w= e > > improved this behavior by: > > 1. allowing only parent classes, not child classes > > 2. requiring that all properties from a real instance have a > corresponding > > one on the proxy OR that the extra properties on the proxy are > skipped/set > > before initialization. > > > > This means that it's now possible for a child class to add a property, > > private or not. There's one requirement: the property must be skipped o= r > > set before initialization. > > > > For the record, with magic methods, we currently have no choice but to > > create an inheritance proxy. This means the situation of having Proxy > > extend Real like in your example is the norm. While doing so, it's pret= ty > > common to attach some interface so that we can augment Real with extra > > capabilities (let's say Proxy implements LazyObjectInterface). Being ab= le > > to use class Real as a backing store for Proxy gives us a very smooth > > upgrade path (the implementation of the laziness can remain an internal > > detail), and it's also sometimes the only way to leverage a factory tha= t > > returns Real, not Proxy. > > I'm not entirely convinced that this is sound now, but I'm not in a > state to think this through in detail. > > 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 b= y > 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? > Properties that are declared on the real instance are bound to the proxy instance, so that accessing any of these properties on the proxy forwards the operation to the corresponding property on the real instance. This includes properties used with ReflectionProperty::skipLazyInitialization() or setRawValueWithoutLazyInitialization(). > 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 by child classes of the real object that the proxy implements). > > 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 w= e > > 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 is > effectively replaced by the following: > > function (object $clonedProxy) use ($originalProxy) { > return clone $originalProxy->getRealObject(); > } > Nope, that's not what we describe in the RFC so I hope you can read it again and get where you were confused and tell us if we're not clear enough (to me we are :) ) The $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. > ? 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 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 independent > after cloning, as I would expect from the clone operation. > That's basically what we do and what we describe in the RFC, just with the added lazy-clone operation on the instance returned by the initializer. > > Any access to a non-existant (i.e. dynamic) property will trigger > >> initialization and this is not preventable using > >> 'skipLazyInitialization()' and 'setRawValueWithoutLazyInitialization()= ' > >> because these only work with known properties? > >> > >> While dynamic properties are deprecated, this should be clearly spelle= d > >> out in the RFC for voters to make an informed decision. > > > > > > Absolutely. From a behavioral PoV, dynamic vs non-dynamic properties > > doesn't matter: both kinds are uninitialized at this stage and the engi= ne > > will trigger object handlers in the same way (it will just not trigger > the > > same object handlers). > > > > Unless I missed it, you didn't update the RFC to mention this. Please do > so, I find it important to have a record of all details that were > discussed (e.g. for the documentation or when evaluating bug reports). > Updated. > > > If the object is already lazy, a ReflectionException is thrown wit= h > >> the message =E2=80=9CObject is already lazy=E2=80=9D. > >> > >> What happens when calling the method on a *initialized* proxy object? > >> i.e. the following: > >> > >> class Obj { public function __construct(public string $name) {} = } > >> $obj1 =3D new Obj('obj1'); > >> $r->resetAsLazyProxy($obj, ...); > >> $r->initialize($obj); > >> $r->resetAsLazyProxy($obj, ...); > >> > >> What happens when calling it for the actual object of an initialized > >> proxy object? > > > > > > Once initialized, a lazy object should be indistinguishable from a > non-lazy > > one. > > This means that the second call to resetAsLazyProxy will just do that: > > reset the object like it does for any regular object. > > > > > > > >> It's probably not possible to prevent this, but will this > >> allow for proxy chains? Example: > >> > >> class Obj { public function __construct(public string $name) {} = } > >> $obj1 =3D new Obj('obj1'); > >> $r->resetAsLazyProxy($obj1, function () use (&$obj2) { > >> $obj2 =3D new Obj('obj2'); > >> return $obj2; > >> }); > >> $r->resetAsLazyProxy($obj2, function () { > >> return new Obj('obj3'); > >> }); > >> var_dump($obj1->name); // what will this print? > > > > > > This example doesn't work because $obj2 doesn't exist when trying to ma= ke > > it lazy but you probably mean this instead? > > Ah, yes you are right. An initialization is missing in the middle of the > two `reset` calls (like in the previous example). My question was > specifically about resetting an initialized proxy, so your adjusted > example is *not quite* what I was looking for, but the results should > probably be the same? > I guess so yes if I understood you correctly. > > > > class Obj { public function __construct(public string $name) {} } > >> $obj1 =3D new Obj('obj1'); > >> $obj2 =3D new Obj('obj2'); > >> $r->resetAsLazyProxy($obj1, function () use ($obj2) { > >> return $obj2; > >> }); > >> $r->resetAsLazyProxy($obj2, function () { > >> return new Obj('obj3'); > >> }); > >> var_dump($obj1->name); // what will this print? > > > > > > This will print "obj3": each object is separate from the other from a > > behavioral perspective, but with such a chain, accessing $obj1 will > trigger > > its initializer and will then access $obj2->name, which will trigger th= e > > second initializer then access $obj3->name, which contains "obj3". > > (I just confirmed with the implementation I have, which is from a > previous > > API flavor, but the underlying mechanisms are the same). > > Okay, that works as expected then. > > > Please let me know if any topics remain unanswered. > > I've indeed found two more questions. > > 1. > > Just to confirm my understanding: The RFC mentions that the initializer > of a proxy receives the proxy object as the first parameter. It further > mentions that making changes is legal (but likely useless). > > My understanding is that attempting to read a property of the > initializer object will most likely fail, because it still is > uninitialized? Or are the properties of the proxy object initialized > with their default value before calling the initializer? > RFC updated. Those properties will remain uninitialized for proxies. > For ghost objects the behavior is clear, just not for proxies. > > 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 would > be observable because the initialization emits an error. Are there any > other reasons? > That's because this is observable using e.g. (array) or var_dump. Nicolas --0000000000009a1827061cf49bdb Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Le=C2=A0ven. 5 juil. 2024 =C3=A0=C2=A021:49, Tim D=C3=BCsterhus <= tim@bastelstu.be&= gt; a =C3=A9crit=C2=A0:
Hi

On 7/2/24 16:48, Nicolas Grekas wrote:
> Thanks for the detailed feedback again, it's very helpful!
> Let me try to answer many emails at once, in chronological order:

Note that this kind of bulk reply make it very hard for me to keep track of mailing list threads. It breaks threading, which makes it much harder for me to find original context of a quoted part, especially since you
did not include the author / date for the quotes.

=
Noted.

=C2=A0
That said, I've taken a look at the differences since my email and also=
gave the entire RFC another read.

> don't touch `readonly` because of lazy objects: this feature is to= o niche
>> to cripple a major-major feature like `readonly`. I would suggest = deferring
>> until after the first bits of this RFC landed.
>>
>
> Following Marco's advice, we've decided to remove all the flag= s related to
> the various ways to handle readonly. This also removes the secondary v= ote.
> The behavior related to readonly properties is now that they are skipp= ed if
> already initialized when calling resetAsLazy* methods, throw in the > initializer as usual, and are resettable only if the class is not fina= l, as
> already allowed in userland (and as explained in the RFC).

The 'readonly' section still mentions 'makeInstanceLazy', w= hich likely
is a left-over from a previous version of the RFC. You should have
another look and clean up the naming there.

=
I found a few other outdated occurrences. They should all be updated n= ow.

=C2=A0
>>> There are not many reasons to do that. The only indented use-c= ase that
>>> doesn't involve an object freshly created with
>>> ->newInstanceWithoutConstructor() is to let an object manag= e its own
>>> laziness by making itself lazy in its constructor:
>>>
>>
>> Okay. But the RFC (and your email) does not explain why I would wa= nt do
>> that. It appears that much of the RFC's complexity (e.g. aroun= d readonly
>> properties and destructors) stems from the wish to support turning= an
>> existing object into a lazy object. If there is no strong reason t= o
>> support that, I would suggest dropping that. It could always be ad= ded in
>> a future PHP version.
>>
>
> This capability is needed for two reasons: 1. completeness and 2. feat= ure
> parity with what can be currently done using magic methods (so that it= 's
> already used to solve real-world problems).

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 objec= t
that makes itself lazy in its own constructor: I have not yet seen a
real-world example.

Keeping this capabi= lity 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 b= y the RFC).

Here is a real-world example:
https://github.c= om/doctrine/DoctrineBundle/blob/2.12.x/src/Repository/LazyServiceEntityRepo= sitory.php

This class currently uses a poor-ma= n's implementation of lazy objects and would greatly benefit from reset= AsLazyGhost().

=C2=A0
> True, thanks for raising this point. After brainstorming with Arnaud, = we
> improved this behavior by:
> 1. allowing only parent classes, not child classes
> 2. requiring that all properties from a real instance have a correspon= ding
> one on the proxy OR that the extra properties on the proxy are skipped= /set
> before initialization.
>
> This means that it's now possible for a child class to add a prope= rty,
> private or not. There's one requirement: the property must be skip= ped or
> set before initialization.
>
> For the record, with magic methods, we currently have no choice but to=
> create an inheritance proxy. This means the situation of having Proxy<= br> > extend Real like in your example is the norm. While doing so, it's= pretty
> common to attach some interface so that we can augment Real with extra=
> capabilities (let's say Proxy implements LazyObjectInterface). Bei= ng able
> to use class Real as a backing store for Proxy gives us a very smooth<= br> > upgrade path (the implementation of the laziness can remain an interna= l
> detail), and it's also sometimes the only way to leverage a factor= y that
> returns Real, not Proxy.

I'm not entirely convinced that this is sound now, but I'm not in a=
state to think this through in detail.

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 ReflectionP= roperty::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 re= worded this sentence a bit. Clearer?

> Properti= es that are declared on the=20 real instance are bound to the proxy instance, so that accessing any of=20 these properties on the proxy forwards the operation to the=20 corresponding property on the real instance. This includes properties=20 used with ReflectionProperty::skipLazyInitializati= on() or setRawValueWithoutLaz= yInitialization().
=C2=A0<= /div>
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 by child classes of the real objec= t that the proxy implements).

=C2=A0
> That is very true. I had a look at the userland implementation and ind= eed,
> we keep the wrapper while cloning the backing instance (it's not t= hat 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 is effectively replaced by the following:

=C2=A0 =C2=A0 =C2=A0function (object $clonedProxy) use ($originalProxy) { =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return clone $originalProxy->getRealOb= ject();
=C2=A0 =C2=A0 =C2=A0}

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

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

=C2=A0
? Then I believe this is unsound. Consider the following:

=C2=A0 =C2=A0 =C2=A0$myProxy =3D $r->newLazyProxy(...);
=C2=A0 =C2=A0 =C2=A0$clonedProxy =3D clone $myProxy;
=C2=A0 =C2=A0 =C2=A0$r->initialize($myProxy);
=C2=A0 =C2=A0 =C2=A0$myProxy->someProp++;
=C2=A0 =C2=A0 =C2=A0var_dump($clonedProxy->someProp);

The clone was created before `someProp` was modified, but it outputs the value after modification!

Also: What happens if the cloned proxy is initialized *before* the
original proxy? There is no real object to clone.

I believe the correct behavior would be: Just clone the proxy and keep
the same initializer. Then both proxies are actually fully independent
after cloning, as I would expect from the clone operation.
=

That's basically what we do and what we describe in= the RFC, just with the added lazy-clone operation on the instance returned= by the initializer.

=C2=A0
>=C2=A0 =C2=A0 Any access to a non-existant (i.e. dynamic) property will= trigger
>> initialization and this is not preventable using
>> 'skipLazyInitialization()' and 'setRawValueWithoutLazy= Initialization()'
>> because these only work with known properties?
>>
>> While dynamic properties are deprecated, this should be clearly sp= elled
>> out in the RFC for voters to make an informed decision.
>
>
> Absolutely. From a behavioral PoV, dynamic vs non-dynamic properties > doesn't matter: both kinds are uninitialized at this stage and the= engine
> will trigger object handlers in the same way (it will just not trigger= the
> same object handlers).
>

Unless I missed it, you didn't update the RFC to mention this. Please d= o
so, I find it important to have a record of all details that were
discussed (e.g. for the documentation or when evaluating bug reports).
<= /blockquote>

Updated.
=C2=A0
>=C2=A0 =C2=A0 > If the object is already lazy, a ReflectionException= is thrown with
>> the message =E2=80=9CObject is already lazy=E2=80=9D.
>>
>> What happens when calling the method on a *initialized* proxy obje= ct?
>> i.e. the following:
>>
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0class Obj { public function __construct(= public string $name) {} }
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$obj1 =3D new Obj('obj1');
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->resetAsLazyProxy($obj, ...);
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->initialize($obj);
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->resetAsLazyProxy($obj, ...);
>>
>> What happens when calling it for the actual object of an initializ= ed
>> proxy object?
>
>
> Once initialized, a lazy object should be indistinguishable from a non= -lazy
> one.
> This means that the second call to resetAsLazyProxy will just do that:=
> reset the object like it does for any regular object.
>
>
>
>> It's probably not possible to prevent this, but will this
>> allow for proxy chains? Example:
>>
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0class Obj { public function __construct(= public string $name) {} }
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$obj1 =3D new Obj('obj1');
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->resetAsLazyProxy($obj1, function = () use (&$obj2) {
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$obj2 =3D new Obj('obj= 2');
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return $obj2;
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0});
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->resetAsLazyProxy($obj2, function = () {
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return new Obj('obj3&#= 39;);
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0});
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0var_dump($obj1->name); // what will t= his print?
>
>
> This example doesn't work because $obj2 doesn't exist when try= ing to make
> it lazy but you probably mean this instead?

Ah, yes you are right. An initialization is missing in the middle of the two `reset` calls (like in the previous example). My question was
specifically about resetting an initialized proxy, so your adjusted
example is *not quite* what I was looking for, but the results should
probably be the same?

I guess so yes if= I understood you correctly.

=C2=A0
>
>=C2=A0 =C2=A0 =C2=A0 =C2=A0class Obj { public function __construct(publ= ic string $name) {} }
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$obj1 =3D new Obj('obj1');
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$obj2 =3D new Obj('obj2');
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->resetAsLazyProxy($obj1, function = () use ($obj2) {
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return $obj2;
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0});
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0$r->resetAsLazyProxy($obj2, function = () {
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return new Obj('obj3&#= 39;);
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0});
>>=C2=A0 =C2=A0 =C2=A0 =C2=A0var_dump($obj1->name); // what will t= his print?
>
>
> This will print "obj3": each object is separate from the oth= er from a
> behavioral perspective, but with such a chain, accessing $obj1 will tr= igger
> its initializer and will then access $obj2->name, which will trigge= r the
> second initializer then access $obj3->name, which contains "ob= j3".
> (I just confirmed with the implementation I have, which is from a prev= ious
> API flavor, but the underlying mechanisms are the same).

Okay, that works as expected then.

> Please let me know if any topics remain unanswered.

I've indeed found two more questions.

1.

Just to confirm my understanding: The RFC mentions that the initializer of a proxy receives the proxy object as the first parameter. It further mentions that making changes is legal (but likely useless).

My understanding is that attempting to read a property of the
initializer object will most likely fail, because it still is
uninitialized? Or are the properties of the proxy object initialized
with their default value before calling the initializer?

RFC updated. Those properties will remain uninitialized f= or proxies.

=C2=A0
For ghost objects the behavior is clear, just not for proxies.

2.

=C2=A0> 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 would be observable because the initialization emits an error. Are there any
other reasons?

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

Nico= las
--0000000000009a1827061cf49bdb--