Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:123743 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 CC2721A009C for ; Fri, 21 Jun 2024 22:22:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1719008636; bh=erif0yyV9Wk+91Hoz0r6lhucEImJeNbmooCjmzHOv3Q=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=ESv6/wcn03HFi6J9kuqazoCp5V6Jl5AizGZP/OeCx9TL7j2Ei8K4yQNyEDoUF0K4a gkwRkSDvhHHo2Sybie0Duz7M+ChjR/j/ci7QgnBZANRQmRb2aw9QyvHDNk55X0nHLb nwaR9OZpV1TNuC5/FNd8aPSfXOJ3Hy2kMGPmYqUoECiPwmnVBZNHxKiNed1PlB/Rk6 6E9P329xz17m3W+OdtPQ+xxf6cRRYeIRgE4xoxaXxxQZA9hWZE/UPzojNQnCrbqtxO pfG8UKngUBCVVtsp8vtVrVK+MWwpOeh0tcomnBMQUNPWG4oEKdBqwSUDwqXkG6US2b IfLjfwUY44YCw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id D921418003F for ; Fri, 21 Jun 2024 22:23:54 +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,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from mail-vs1-f50.google.com (mail-vs1-f50.google.com [209.85.217.50]) (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, 21 Jun 2024 22:23:52 +0000 (UTC) Received: by mail-vs1-f50.google.com with SMTP id ada2fe7eead31-48f450b3159so104527137.3 for ; Fri, 21 Jun 2024 15:22:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=beberlei-de.20230601.gappssmtp.com; s=20230601; t=1719008556; x=1719613356; 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=56PvPMFW7C3kXS7iL20z/Qf+0k0JCdOQZj/6qN1fbf4=; b=djksWXkWS6On5uldWYe/Hazm5RuZuc1aaP99ZqlQGBdPDS/8Of1rJIgigZPemXW0s6 0ymiz1KneU65qVNZGJfO+xHIxIRBbUd2W68z/2I3SKlFVkm++re4Q8QzvlRfvy48wx6Y XeN7p10Z+/UlcSimy8+9pNUdZJom4r5Yn8VfOqWbuzXfxWswZc6f6/ALAOD7jH7m+4ze YiQVer4m9WxOEYdr6WpWd7rMDwvfNS7caOR5PXRN+BrNeKw5nodYZXBuNs2vUYb7K6s7 0AGlgvhbGEWqPJssaM/tgleWFWPXVGrBPsPNjD0NKeuc7Vt/LjLYZiX0Q7xMjOE2zLOo /1nQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1719008556; x=1719613356; 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=56PvPMFW7C3kXS7iL20z/Qf+0k0JCdOQZj/6qN1fbf4=; b=hx/CP/jrHF828xxNC9JW0RdXaU3FWxoqPficTEWdD6Fwm6zqSreu5OxMlFR/BdkHxX LBW1nmNaWBUXyxxgvk+8Ky0a9OJZ+vtTUzOtDVHCreIbzQipPdONWe0syIXbR4PRRXDx xo0KPXilBLVdyycEQKVeBoGy7GcSfQrScwVjjkWz0wjcjmlQ56wqIVnqkWf+yfYOCJOq Ui7f9+6wet9L6tGiJ4gWddAEskyXlsXPz1+4SX+fRLWlB/09gxzdAnu9O8qp6Z8lLPHo jfotRj8AQVcw8oRE4PNoPkjiVAmcYh9sc0+P+HLEU/0RQQXLRHkvCshuEdjKBZZLZzX9 HNVQ== X-Forwarded-Encrypted: i=1; AJvYcCVCxM7wE1lR52BaUmBV3uDq5rCrgS6p1u8dpPcJ1bR4qzmhWi7UF2Lg8YVcvk8CENcflaBfAcjSgpWdkudcjqLXpxAEcYkRiQ== X-Gm-Message-State: AOJu0Yx4G9E0+A5nZh5zgj5Pt9f7Muh5tYtvky9c7+WB2OYtgg/bTzsy JNT3M8iPXhU0jFb7VYtuEPZhz769xI/TEao4QgXF1Vc/3qwptRWeohzOc/WY6xfz29jbe4Yb2mQ DsAG6OpoHHYqTNBptk6vnDA7v40mu3ienWM658A== X-Google-Smtp-Source: AGHT+IF7T9IrD+bficlcWUoKED8LFpuetRMpLWZk76GOFfar9h+KOsoVuhlXFooNXQLzQBg4pNa9K3UZye7Lz3Awasc= X-Received: by 2002:a67:e34b:0:b0:48f:42f5:dd0d with SMTP id ada2fe7eead31-48f42f5df7fmr1714519137.25.1719008556622; Fri, 21 Jun 2024 15:22:36 -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> <8fc1969d-5f21-400d-a3ae-aee5d741a81d@app.fastmail.com> In-Reply-To: Date: Sat, 22 Jun 2024 00:22:25 +0200 Message-ID: Subject: Re: [PHP-DEV] [RFC] Lazy Objects To: Nicolas Grekas Cc: Larry Garfield , php internals Content-Type: multipart/alternative; boundary="00000000000062437d061b6dde24" From: kontakt@beberlei.de (=?UTF-8?Q?Benjamin_Au=C3=9Fenhofer?=) --00000000000062437d061b6dde24 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Fri, Jun 21, 2024 at 12:24=E2=80=AFPM Nicolas Grekas < nicolas.grekas+php@gmail.com> wrote: > Hi Ben, > > On Tue, Jun 18, 2024, at 5:45 PM, Arnaud Le Blanc wrote: >>>> > Hi Larry, >>>> > >>>> > Following your feedback we propose to amend the API as follows: >>>> > >>>> > ``` >>>> > class ReflectionClass >>>> > { >>>> > public function newLazyProxy(callable $factory, int $options): >>>> object {} >>>> > >>>> > public function newLazyGhost(callable $initializer, int >>>> $options): object {} >>>> > >>>> > public function resetAsLazyProxy(object $object, callable >>>> > $factory, int $options): void {} >>>> > >>>> > public function resetAsLazyGhost(object $object, callable >>>> > $initializer, int $options): void {} >>>> > >>>> > public function initialize(object $object): object {} >>>> > >>>> > public function isInitialized(object $object): bool {} >>>> > >>>> > // existing methods >>>> > } >>>> > >>>> > class ReflectionProperty >>>> > { >>>> > public function setRawValueWithoutInitialization(object $object, >>>> > mixed $value): void {} >>>> > >>>> > public function skipInitialization(object $object): void {} >>>> > >>>> > // existing methods >>>> > } >>>> > ``` >>>> > >>>> > Comments / rationale: >>>> > - Adding methods on ReflectionClass instead of ReflectionObject is >>>> > better from a performance point of view, as mentioned earlier >>>> > - Keeping the word "Lazy" in method names is clearer, especially for >>>> > "newLazyProxy" as a the "Proxy" pattern has many uses-cases that are >>>> > not related to laziness. However we removed the word "Instance" to >>>> > make the names shorter. >>>> > - We have renamed "make" methods to "reset", following your feedback >>>> > about the word "make". It should better convey the behavior of these >>>> > methods, and clarify that it's modifying the object in-place as well >>>> > as resetting its state >>>> > - setRawValueWithoutInitialization() has the same behavior as >>>> > setRawValue() (from the hooks RFC), except it doesn't trigger >>>> > initialization >>>> > - Renamed $initializer to $factory for proxy methods >>>> > >>>> > WDYT? >>>> > >>>> > Best Regards, >>>> > Arnaud >>>> >>>> Oh, that looks *so* much more self-explanatory and readable. I love >>>> it. Thanks! (Looks like the RFC text hasn't been updated yet.) >>>> >>> >>> Happy you like it so much! The text of the RFC is now up to date. Note >>> that we renamed ReflectionProperty::skipInitialization() and >>> setRawValueWithoutInitialization() to skipLazyInitialization() and >>> setRawValueWithoutLazyInitialization() after we realized that >>> ReflectionProperty already has an isInitialized() method for something >>> quite different. >>> >>> While Arnaud works on moving the code to the updated API, are there mor= e >>> comments on this RFC before we consider opening the vote? >>> >> >> Thank you for updating the API, the RFC is now much easier to grasp. >> >> >> My few comments on the updated RFC: >> >> >> 1 ) ReflectionClass API is already very large, adding methods should use >> naming carefully to make sure that users identify them as belonging to a >> sub.feature (lazy objects) in particular, so i would prefer we rename so= me >> of the new methods to: >> >> >> isInitialized =3D> isLazyObject (with inverted logic) >> >> initialize =3D> one of initializeLazyObject / initializeWhenLazy / >> lazyInitialize - other methods in this RFC are already very outspoken, s= o I >> don=E2=80=99t mind being very specific here as well. >> >> >> The reason is =E2=80=9Einitialized=E2=80=9C is such a generic word, best= not have API >> users make assumptions about what this relates to (readonly, lazy, =E2= =80=A6) >> > > I get this aspect, I'm fine with either option, dunno if anyone has a > strong preference? > Under this argument, mine is isLazyObject + initializeLazyObject. > > > 2.) I am 100% behind the implementation of lazy ghosts, its really great >> work with all the behaviors. Speaking with my Doctrine ORM core develope= r >> hat this has my full support. >> > > \o/ > > >> >> 3.) the lazy proxies have me worried that we are opening up a can of >> worms by having the two objects and the magic of using only the properti= es >> of one and the methods of the other. >> >> >> Knowing Symfony DIC, the use case of a factory method for the proxy is a >> compelling argument for having it, but it is a leaky abstraction solving >> the identity issue only on one side, but the factory code might not know >> its used for a proxy and make all sorts of decisions based on identity t= hat >> lead to problems. >> >> >> Correct me if i am wrong or missing something, but If the factory does >> not know about proxying, then it would also be fine to build a lazy ghos= t >> and copy over all state after using the factory. >> > > Unfortunately no, copying doesn't work in the generic case: when the > object's dependencies involve a circular reference with the object itself= , > the copying strategy can lead to a sort of "brain split" situation where = we > have two objects (the proxy and the real object) which still coexist but > can have diverging states. > > This is what virtual state proxies solve, by making sure that while we > have two objects, we're sure by design that they have synchronized state. > > Yes, $this can leak with proxies, but this is reduced to the strict > minimum in the state-proxy design. Compared to the "brain split" I > mentioned, this is a minor concern. > > State-synchronization is costly currently since it relies on magic method= s > on every single property access. > From this angle, state-proxies are the ones that benefit the most from > being in the engine. > Makes sense to me. > > > 4.) I am wondering, do we need the resetAs* methods? You can already >> implement lazy proxies in userland code by manually writing the code, we >> don=E2=80=99t need engine support for that. Not having these two methods= would >> reduce the surface of the RFC / API considerably. And given the =E2=80= =9Ereal >> world=E2=80=9C example is not really real world, only the Doctrine >> (createLazyGhost) and Symfony (createLazyGhost or createLazyProxy) are, >> this shows maybe its not needed. >> > > Yes, this use case of making an object lazy after it's been created is > quite useful. It makes it straightforward to turn a class lazy using > inheritance for example (LazyClass extends NonLazyClass), without having = to > write nor maintain any decorating logic. From a technical pov, this is ju= st > a different flavor of the same code infrastructure, so this is pretty > aligned with the rest of the proposed API. > Will you use this in Symfony DIC for something? While I can understand the argument that its easy to integrate with the lazy object code that you have, i don't see how the argument "without having to write nor maintain any dceoarting logic" is true. The example in the RFC is very much written code. Yes its only one line of new ReflectionClass()->initialie($this)->send($data), but something like https://gist.github.com/beberlei/568719a1c5536cc5f59a60381c37aa05 is not more code and works fine. The (Lazy-)Connection example can be re-written as: class LazyConnection extends Connection { public function create(): Connection { return (new ReflectionClass(Connection::class))->newLazyGhost(function (Connection $connection) { $connection->__construct(); // Or any heavier initialization logic $connection->ttl =3D 2.0; }); } private function __construct() { parent::__construct(); } } This to me reads easier, especially when Connection has more than one public method (send) it requires way less code. Given the complexities of newLazy* already, i am just trying to find arguments to keep the public surface of this API as small as posisble, as its intricacies are hard to grasp and simplicity / less ways to use it will be a benefit. So far i don't see that with resetAsLazy* you can impmlement something new that cannot also be done with newLazy* methods. > > >> 5.) The RFC does not spell it out, but I assume this does not have any >> effect on stacktraces, i.e. since properties are proxied, there are no >> =E2=80=9Emagic=E2=80=9C frames appearing in the stacktraces? >> > > Nothing special on this domain indeed, there are no added frames (unlike > inheritance proxies since they'd decorate methods). > > As a general note, an important design criterion for the RFC has been to > make it a superset of what we can achieve in userland already. Ghost > objects, state proxies, capabilities of resetAsLazy* methods, etc are all > possible today. Making the RFC a subset of those existing capabilities > would defeat the purpose of this proposal, since it would mean we'd have = to > keep maintaining the existing code to support the use cases it enables, > with all the associated drawbacks for the PHP community at large. > I very much appreciate the benefits this brings as primary language concept= . > > Nicolas > > --00000000000062437d061b6dde24 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable



On Fri, Jun 21, 2024 at 12:24=E2=80=AFPM Nicolas Grekas <nicolas.grekas= +php@gmail.com> wrote:
Hi Ben,

=
On Tue, Jun 18, 2024, at 5:4= 5 PM, Arnaud Le Blanc wrote:
> Hi Larry,
>
> Following your feedback we propose to amend the API as follows:
>
> ```
> class ReflectionClass
> {
>=C2=A0 =C2=A0 =C2=A0public function newLazyProxy(callable $factory, int= $options): object {}
>
>=C2=A0 =C2=A0 =C2=A0public function newLazyGhost(callable $initializer,= int $options): object {}
>
>=C2=A0 =C2=A0 =C2=A0public function resetAsLazyProxy(object $object, ca= llable
> $factory, int $options): void {}
>
>=C2=A0 =C2=A0 =C2=A0public function resetAsLazyGhost(object $object, ca= llable
> $initializer, int $options): void {}
>
>=C2=A0 =C2=A0 =C2=A0public function initialize(object $object): object = {}
>
>=C2=A0 =C2=A0 =C2=A0public function isInitialized(object $object): bool= {}
>
>=C2=A0 =C2=A0 =C2=A0// existing methods
> }
>
> class ReflectionProperty
> {
>=C2=A0 =C2=A0 =C2=A0public function setRawValueWithoutInitialization(ob= ject $object,
> mixed $value): void {}
>
>=C2=A0 =C2=A0 =C2=A0public function skipInitialization(object $object):= void {}
>
>=C2=A0 =C2=A0 =C2=A0// existing methods
> }
> ```
>
> Comments / rationale:
> - Adding methods on ReflectionClass instead of ReflectionObject is
> better from a performance point of view, as mentioned earlier
> - Keeping the word "Lazy" in method names is clearer, especi= ally for
> "newLazyProxy" as a the "Proxy" pattern has many u= ses-cases that are
> not related to laziness. However we removed the word "Instance&qu= ot; to
> make the names shorter.
> - We have renamed "make" methods to "reset", follo= wing your feedback
> about the word "make". It should better convey the behavior = of these
> methods, and clarify that it's modifying the object in-place as we= ll
> as resetting its state
> - setRawValueWithoutInitialization() has the same behavior as
> setRawValue() (from the hooks RFC), except it doesn't trigger
> initialization
> - Renamed $initializer to $factory for proxy methods
>
> WDYT?
>
> Best Regards,
> Arnaud

Oh, that looks *so* much more self-explanatory and readable.=C2=A0 I love i= t.=C2=A0 Thanks!=C2=A0 (Looks like the RFC text hasn't been updated yet= .)

Happy you like it so much! The text = of the RFC is now up to date. Note that we renamed ReflectionProperty::skip= Initialization() and setRawValueWithoutInitialization() to skipLazyInitiali= zation() and setRawValueWithoutLazyInitialization() after we realized that = ReflectionProperty already has an isInitialized() method for something quit= e different.

While Arnaud works on moving the code= to the updated API, are there more comments on this RFC before we consider= opening the vote?

Thank you for updating the API, the RF= C is now much easier to grasp.


=

My few comments on the upda= ted RFC:


=

1 ) ReflectionClass API is = already very large, adding methods should use naming carefully to make sure= that users identify them as belonging to a sub.feature (lazy objects) in p= articular, so i would prefer we rename some of the new methods to:


=

=C2=A0isInitialized =3D>= isLazyObject (with inverted logic)

initialize =3D> one of i= nitializeLazyObject / initializeWhenLazy / lazyInitialize - other methods i= n this RFC are already very outspoken, so I don=E2=80=99t mind being very s= pecific here as well.


=

The reason is =E2=80=9Einit= ialized=E2=80=9C is such a generic word, best not have API users make assum= ptions about what this relates to (readonly, lazy, =E2=80=A6)


I get this aspect, I'm fine w= ith either option, dunno if anyone has a strong preference?
Under= this argument, mine is isLazyObject + initializeLazyObject.


2.) =C2=A0I am 100% behind = the implementation of lazy ghosts, its really great work with all the behav= iors. Speaking with my Doctrine ORM core developer hat this has my full sup= port.


\o/
<= div>=C2=A0


=

3.) =C2=A0the lazy proxies = have me worried that we are opening up a can of worms by having the two obj= ects and the magic of using only the properties of one and the methods of t= he other.=C2=A0


=

Knowing Symfony DIC, the us= e case of a factory method for the proxy is a compelling argument for havin= g it, but it is a leaky abstraction solving the identity issue only on one = side, but the factory code might not know its used for a proxy and make all= sorts of decisions based on identity that lead to problems.


=

Correct me if i am wrong or= missing something, but If the factory does not know about proxying, then i= t would also be fine to build a lazy ghost and copy over all state after us= ing the factory.


Unfortunately no, copying doesn't work in the generic case: when the = object's dependencies involve a circular reference with the object itse= lf, the copying strategy can lead to a sort of "brain split" situ= ation where we have two objects (the proxy and the real object) which still= coexist but can have diverging states.

This is wh= at virtual state proxies solve, by making sure that while we have two objec= ts, we're sure by design that they have synchronized state.
<= br>
Yes, $this can leak with proxies, but this is reduced to the = strict minimum in the state-proxy design. Compared to the "brain split= " I mentioned, this is a minor concern.

S= tate-synchronization is costly currently since it relies on magic methods o= n every single property access.
From this angle, state-proxie= s are the ones that benefit the most from being in the engine.

Makes sense to me.


4.) =C2=A0I am wondering, d= o we need the resetAs* methods? You can already implement lazy proxies in u= serland code by manually writing the code, we don=E2=80=99t need engine sup= port for that. Not having these two methods would reduce the surface of the= RFC / API considerably. And given the =E2=80=9Ereal world=E2=80=9C example= is not really real world, only the Doctrine (createLazyGhost) and Symfony = (createLazyGhost or createLazyProxy) are, this shows maybe its not needed.<= /span>


Yes, this use case o= f making an object lazy after it's been created is quite useful. It mak= es it straightforward to turn a class lazy using inheritance for example (L= azyClass extends NonLazyClass), without having to write nor maintain any de= corating logic. From a technical pov, this is just a different flavor of th= e same code infrastructure, so this is pretty aligned with the rest of the = proposed API.

Will you us= e this in Symfony DIC for something? While I can understand the argument th= at its easy to integrate with the lazy object code that you have, i don'= ;t see how the argument "without having to write nor maintain any dceo= arting logic" is true.=C2=A0

The example in t= he RFC is very much written code. Yes its only one line of new ReflectionCl= ass()->initialie($this)->send($data), but something like https://g= ist.github.com/beberlei/568719a1c5536cc5f59a60381c37aa05 is not more co= de and works fine.

The (Lazy-)Connection example c= an be re-written as:

class LazyConnection extends = Connection
{
=C2=A0 =C2=A0 public function create(): Connection
= =C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 return (new ReflectionClass(= Connection::class))->newLazyGhost(function (Connection $connection) {=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 $connection->__construct(); /= / Or any heavier initialization logic
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 $connection->ttl =3D 2.0;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 });
= =C2=A0 =C2=A0 }
=C2=A0 =C2=A0
=C2=A0 =C2=A0 private function __const= ruct() {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 parent::__construct();
=C2=A0 = =C2=A0 }
}

This to me reads easier, especia= lly when Connection has more than one public method (send) it requires way = less code.

Given the complexities of newLazy* alre= ady, i am just trying to find arguments to keep the public surface of this = API as small as posisble, as its intricacies are hard to grasp and simplici= ty / less ways to use it will be a benefit.=C2=A0

= So far i don't see that with resetAsLazy* you can impmlement something = new that cannot also be done with newLazy* methods.=C2=A0
=C2=A0<= /div>
=C2= =A0
5.) The RFC does = not spell it out, but I assume this does not have any effect on stacktraces= , i.e. since properties are proxied, there are no =E2=80=9Emagic=E2=80=9C f= rames appearing in the stacktraces?=C2=A0

Nothing special on this domain indeed, there are no= added frames (unlike inheritance proxies since they'd decorate methods= ).

As a general note, an important design criterio= n for the RFC has been to make it a superset of what we can achieve in user= land already. Ghost objects, state proxies, capabilities of resetAsLazy* me= thods, etc are all possible today. Making the RFC a subset of those existin= g capabilities would defeat the purpose of this proposal, since it would me= an we'd have to keep maintaining the existing code to support the use c= ases it enables, with all the associated drawbacks for the PHP community at= large.

I very much a= ppreciate the benefits this brings as primary language concept.

=
Nicolas

--00000000000062437d061b6dde24--