Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:123664 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 8709D1A009C for ; Tue, 18 Jun 2024 17:45:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1718732787; bh=RgDbbNcb2Dl3gt5qe5YC83pw/0rkVsLZKIJDkT/aehg=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=NC8naJeSobbaC06bIBJY7ihH03rTU/uBMoTuZgY/TuB17vSyxB76dMZPfL0jYfzSw D5MaOUldJsNOOBw1flie90jXx9TJczjSGN25K/q6m2PxHYVBmt/1M0i+e79FzlW+Ji KPEqWLsxmKeWY5o/+tXvJGj5RQGFPnBUHjo91Izlh9XP20JWPry7YLrCdMzvu8GkGT iFyYcx6ntfQkIKxjchOLmAH5G/oxNCMptOQN4+q7mI2NpPvrWE9VlJ1TZnc1MbyEfl eGW8qQaHT9SUrdZDCHiTnui4zXH0EXGPd9abTXJ1wYALCAyrEk3r+qKjB73U9cO3xD LO5J0han+GFcg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id ABAAE180690 for ; Tue, 18 Jun 2024 17:46:26 +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, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS, 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-ed1-f44.google.com (mail-ed1-f44.google.com [209.85.208.44]) (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 ; Tue, 18 Jun 2024 17:46:26 +0000 (UTC) Received: by mail-ed1-f44.google.com with SMTP id 4fb4d7f45d1cf-57ccd1111aeso3806037a12.0 for ; Tue, 18 Jun 2024 10:45:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1718732712; x=1719337512; darn=lists.php.net; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=2X0FQNSwlkATROauVYTGRrpGNWSeKlkT0FywgJBHQCU=; b=HTfZYyTJ1jwJnyrWSYu/ikYA3cnRmcI1MO1s+tntZaaHh0BStUnF6UjaBkGO6NPfGk AFkO3JIf4FJGIqys9EzcXtfaqe4oEHhJ1sMBZgqRNyf/dP6QtHJyOvYMW9WP3b4uf3k6 VKnQSE/JzITUCEBpzfyzqlROl0nB4HjYT9O8QCdxlJiV1Mmy8jZK84/4pZQNS7GG7UeI qwq2QJY1rqqxTdDehFVYgGmhPkVUQ7Tgi/qyWsOfbkaV8se/0lIcDzDeJhjQ/FxSCku2 QbwuUPEhn1j8vOS9O5AvwBO9XnrLAGM6hWRkFClt/qm4p37gv3hd303jIowcgKXN+x7j 18aA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1718732712; x=1719337512; h=content-transfer-encoding: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=2X0FQNSwlkATROauVYTGRrpGNWSeKlkT0FywgJBHQCU=; b=agUVAZ5cLrO7NK1bMRIvfLYRSSG+bHDNqzOV4RB0jm/xWbZ/+LK7Nm3OZe/25F2qAA PSBaunrGWC6wSMVdqh8qs/p0vqWC/5xsewQMcbQxKT/mH/PJ90xKGhvc39AqPO0Ex2OZ jReg8ryWrkkQBrH0T2Sf5TksRuP3XXZnkYlhZPxPUSrVJUNPkBPdwiqECdXjWMiXVQXU 7W/MdPbgPSeikunDl5d4fk11dHYE6pzOcWV6r+bWpYyVLyLASuSs6aJtwamm/IkrYo/4 sHVd8f1jYMnuhOOqvcWa5or01wB+cPIVDmJUmp1Q508POGPb6Cre1K224Lr64QOOTerX UJIQ== X-Gm-Message-State: AOJu0YyfWBhxuLwwdc7R+87OS5Vpzt8oGqfCZ8Wk+10iQuw06PS4OUUv 51klXTSFQFDU3mKVP1oHxyQLB5N2TCkxj8mWS0LNR18O7bUOQCX2NROh6x9csU1nBBC1Xb3DH9u pp50EN7PalcfKt0iWcvAJPoaiS6NVuV4/ X-Google-Smtp-Source: AGHT+IEoVGKp+W4MADcOXTi/UlLgOFGfM9TmXTJiKzvKBJu1h3yFUMAfYiid8IEeJImvT+m6FcOHja29qLuka+PUcHs= X-Received: by 2002:a50:951e:0:b0:57c:5503:940b with SMTP id 4fb4d7f45d1cf-57d07e48fdfmr125222a12.15.1718732711959; Tue, 18 Jun 2024 10:45:11 -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> In-Reply-To: Date: Tue, 18 Jun 2024 19:45:00 +0200 Message-ID: Subject: Re: [PHP-DEV] [RFC] Lazy Objects To: Larry Garfield Cc: php internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable From: arnaud.lb@gmail.com (Arnaud Le Blanc) 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): obje= ct {} 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 On Sun, Jun 16, 2024 at 3:46=E2=80=AFPM Arnaud Le Blanc wrote: > > On Sat, Jun 15, 2024 at 7:13=E2=80=AFPM Larry Garfield wrote: > > > In practice I expect there will be two kinds of ghost initializers: > > > - Those that just call one public method of the object, such as the c= onstructor > > > - Those that initialize everything with ReflectionProperty::setValue(= ) > > > as in the Doctrine example in the "About Lazy-Loading strategies" > > > section > > I'm still missing an example with ::bind(). Actually, I tried to write= a version of what I think the intent is and couldn't figure out how. :-) > > > > $init =3D function() use ($c) { > > $this->a =3D $c->get(ServiceA::class); > > $this->b =3D $c->get(ServiceB::class); > > } > > > > $service =3D new ReflectionLazyObjectFactory(Service::class, $init); > > > > // We need to bind $init to $service now, but we can't because $init is= already registered as the initializer for $service, and binding creates a = new closure object, not modifying the existing one. So, how does this even= work? > > Oh I see. Yes you will not be able to bind $this in a simple way here, > but you could bind the scope. This modified example will work: > > ``` > $init =3D function($object) use ($c) { > $object->a =3D $c->get(ServiceA::class); > $object->b =3D $c->get(ServiceB::class); > } > $service =3D new ReflectionLazyObjectFactory(Service::class, > $init->bindTo(null, Service::class)); > ``` > > If you really want to bind $this you could achieve it in a more convolute= d way: > > ``` > $init =3D function($object) use ($c) { > (function () use ($c) { > $this->a =3D $c->get(ServiceA::class); > $this->b =3D $c->get(ServiceB::class); > })->bindTo($object)(); > } > $service =3D new ReflectionLazyObjectFactory(Service::class, $init); > ``` > > This is inconvenient, but the need or use-case is not clear to me. > Could you describe some use-cases where you would hand-write > initializers like this? Do you feel that the proposal should provide > an easier way to change $this and/or the scope? > > > > In practice we expect that makeInstanceLazy*() methods will not be > > > used on fully initialized objects, and that the flag will be set most > > > of the time, but as it is the API is safe by default. > > > > In the case an object does not have a destructor, it won't make a diffe= rence either way, correct? > > Yes > > > >> I find it interesting that your examples list DICs as a use case for= proxies, when I would have expected that to fit ghosts better. The common= pattern, I would think, would be: > > > The RFC didn't make it clear enough that the example was about the > > > factory case specifically. > > > > Ah, got it. That makes more sense. > > > > Which makes me ask if the $initializer of a proxy should actually be ca= lled $factory? Since that's basically what it's doing, > > Good point, $factory would be a good name for this parameter. > > > and I'm unclear what it would do with the proxy object itself that's pa= ssed in. > > Passing the factory itself as argument could be used to make decisions > based on the value of some initialized field, or on the class of the > object, or on its identity. I think Nicolas had a real use-case where > he detects clones based on the identity of the object: > > ``` > $init =3D function ($object) use (&$originalObject) { > if ($object !=3D=3D $originalObject) { > // we are initializing a clone > } > }; > $originalObject =3D $reflector->newProxyInstance($init); > ``` > > This was on ghosts, but I think it's also a valid use-case example for pr= oxies. > > > >> ReflectionLazyObjectFactory is a terrible name. Sorry, it is. :-) = Especially if it's subclassing ReflectionClass. If it were its own thing, = maybe, but it's still too verbose. I know you don't want to put more on th= e "dumping ground" fo ReflectionClass, but honestly, that feels more ergono= mic to me. That way the following are all siblings: > > >> > > >> newInstance(...$args) > > >> newInstanceWithoutConstructor(...$args) > > >> newGhostInstance($init) > > >> newProxyInstance($init) > > >> > > >> That feels a lot more sensible and ergonomic to me. isInitialized()= , initialized(), etc. also feel like they make more sense as methods on Ref= lectionObject, not as static methods on a random new class. > > > > > > Thank you for the suggestion. We will check if this fits the > > > use-cases. Moving some methods on ReflectionObject may have negative > > > performance implications as it requires creating a dedicated instance > > > for each object. Some use-cases rely on caching the reflectors for > > > performance. > > > > > > Best Regards, > > > Arnaud > > > > I'm not clear why there's a performance difference, but I haven't looke= d at the reflection implementation in, well, ever. :-) > > What I meant is that creating an instance (not necessarily of > ReflectionObject, but of any class) is more expensive than just doing > nothing. The first two loops below would be fine, but the last one > would be slower. This can make an important difference in a hot path. > > ``` > foreach ($objects as $object) { > ReflectionLazyObject::isInitialized($object); > } > > $reflector =3D new ReflectionClass(SomeClass::class); > foreach ($objects as $object) { > $reflector->isInitialized($object); > } > > foreach ($objects as $object) { > $reflector =3D new ReflectionObject($object); > $reflector->isInitialized($object); > } > ``` > > > If it has to be a separate object, please don't make it extend Reflecti= onClass but still give it useful dynamic methods rather than static methods= . Or perhaps even do something like > > > > $ghost =3D new ReflectionGhostInstance(SomeClass::class, $init); > > $proxy =3D new ReflectionProxyINstance(SOmeClass::class, $init); > > > > And be done with it. (I'm just spitballing here. As I said, I like th= e feature, I just want to ensure the ergonomics are as good as possible.) > > Thank you for your help. We will think about a better API.