Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:123768 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 6999E1A009C for ; Sun, 23 Jun 2024 23:03:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1719183877; bh=9tChlOI0VAbN6j0c9vyHnGYkwixguFSVhGeLyzDLFTQ=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=kP+piaE3uLcNy71SH4XdzSfoDsVzTqiLdBqdb67MQw/AU4De/H8RE4NOGZPrYNayU Rvt/7xFPNobT2wrVBx5yMPHSimZAcUwAj1eqiaPBcZ4Cmt4SAAyskjYgnzJvnTJJWa vI03oZ3NfEZ+uqteOAQ7qeVf4V/3RT8v1X0R5NCOvea3A3PtauwH48Q5LZHi4piBv5 FO2tp7k5IJ/fJYbaLQ3Qq39cx/sGob5TmxWMSgIek6zDjV1e+n/Ajv14Ok2p9SluDY 1lb5a9jOVEwJzg32PJTruuKYxl8UHQgj4tck2/9tdd5DDs6Q4+rvLwJoiwHm0Uai53 QeXitzaRvcNmQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 75F4218005F for ; Sun, 23 Jun 2024 23:04:34 +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=1.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, FREEMAIL_REPLY,HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL,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-pf1-f182.google.com (mail-pf1-f182.google.com [209.85.210.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 ; Sun, 23 Jun 2024 23:04:34 +0000 (UTC) Received: by mail-pf1-f182.google.com with SMTP id d2e1a72fcca58-70656b43fd4so1601400b3a.0 for ; Sun, 23 Jun 2024 16:03:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1719183797; x=1719788597; 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=OqIWkSGOmfOetXB18l/C1dDu+ggqgxvpIfBX4N35GrU=; b=cxp845ED0zyOLE/lIzzZOIflMOKgSlGLeOs2v8aEjhw7cOHrNkSFQEZpmKWC/oO4v+ loP3uO4QKhUS7nrvKOxBqDcculAWXoky70WhPsqaAKGcdYfKFnxeQ6TRXchdrDjunlgr P3fGL4d95e1Dp+NQ012a9fptcrrr5IdCMNmaur++unlYHhcJC/Lk8s1VVfXHrYXns7CC sW4TzpMUBjiP52ZpD6i3A4olKM4hane5Z0ivz5fHlyUW811tK2Vvs6cevUZ0pHo1ayp2 oPU8u+p6xqyCy7tmQD55Pi7qF+mDPG8b0eSp0IL6gB/+tys9EDB+R0t1eOkvdwtfTsno 2HHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1719183797; x=1719788597; 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=OqIWkSGOmfOetXB18l/C1dDu+ggqgxvpIfBX4N35GrU=; b=gI50TeRsiNw0oCXlKLwDe8A5mzxcr6M/0nxF/vNg8BahWQCy4NBsls8jAZdHKbUupq TIOUqnkDfvE8zPKEts9aYoysTygnannYC8JxE3Jo1B4nSsTSAYaC995JMHBijJg5EwiN XreHUPUIR4R82VixAVjdzjBxtH1IxO8wS2nTyyzX22Vxk1ck9gg5A8wA2bjM6yx2FJxs lpE6BcO8CXQMml++1a8CKhL8y33IuH4qF2NkGa30zBJ0V5b8kQnfyc/JTN9VS4QN/zqP 5je21pl5DdMr+ogKCLBjZ2Z5/gNC+KsT7pqZp1OOlL9khPwS0eZIQl1N7SHnHDI9zWR9 ipqg== X-Gm-Message-State: AOJu0Yx9KXzKrkAcTJTZEMj/ASkaVoMg/j02hrhiSJO+hQkQYMPtmx5X BUP2XapIjCXRT9qkDLxMU8PYa7KUb8fFUmviQ6hQYHz3bYfEk3JhMTcxH4/AEY0ymJ+Jq3ANmmq VFBdLT/Lq3ORJvANZrqIL14qDkrdcnQDuzJA= X-Google-Smtp-Source: AGHT+IFFqfGOSE9knhYAbQ5ANul1mMrDbBSFCkRmg0fy97XfzKRuu3hE2I2Sc9wt8KdQTyPJVoAwKudW6jLa3Er0t7g= X-Received: by 2002:a17:90b:1b0a:b0:2c2:c2fe:77c5 with SMTP id 98e67ed59e1d1-2c861485791mr2108911a91.40.1719183796781; Sun, 23 Jun 2024 16:03:16 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 References: In-Reply-To: Date: Mon, 24 Jun 2024 01:03:04 +0200 Message-ID: Subject: Re: [PHP-DEV] [RFC] Lazy Objects To: Nicolas Grekas Cc: PHP Internals List , Arnaud Le Blanc Content-Type: multipart/alternative; boundary="00000000000082c900061b96ab74" From: ocramius@gmail.com (Marco Pivetta) --00000000000082c900061b96ab74 Content-Type: text/plain; charset="UTF-8" Hey Nicolas, Arnaud, On Tue, 4 Jun 2024 at 14:29, Nicolas Grekas wrote: > Please find all the details here: > https://wiki.php.net/rfc/lazy-objects > First of all, let me say that this is a fantastic RFC: having maintained both mine and Doctrine's version of lazy proxies for many years, this is indeed a push in the right direction, making laziness an engine detail, rather than a complex userland topic with hacks. Moving this forward will allow us (in a far future) to get rid of some BC boundaries around the weird `unset()` semantics of properties, which were indeed problematic with typed properties and `readonly` properties. Stellar work: well done! ## TL;DR: of my feedback: * RFC is good / useful / needed: will vote for it * ghost proxies are well designed / fantastic feature * lazy proxies should probably explore replacing object identity further * don't like expanding `ReflectionClass` further: `LazyGhost` and `LazyProxy` classes (or such) instead * `initialize()` shouldn't have a boolean behavioral switch parameter: make 2 methods * flags should be a `list` instead. A bitmask for a new API feels unsafe and anachronistic, given the tiny performance hit. * don't touch `readonly` because of lazy objects: this feature is too niche to cripple a major-major feature like `readonly`. I would suggest deferring until after the first bits of this RFC landed. That said, I took some notes that I'd like you to both consider / answer. ## Raw feedback I did skim through the thread, but did not read it all, so please excuse me if some feedback is potentially duplicate. Notes are succinct/to the point: I abuse bullet points heavily, sorry :-) > From an abstraction point of view, lazy objects from this RFC are indistinguishable from non-lazy ones * do the following aspects always apply? I understand they don't for lazy proxies, just for ghosts? * `spl_object_id($object) === spl_object_id($proxy)`? * `get_class($object) === get_class($proxy)`? > Execution of methods or property hooks does not trigger initialization until one of them accesses a backed property. * excellent! The entire design revolving around object state makes it so much easier to reason about the entire behavior too! > Proxies: The initializer returns a new instance, and interactions with the proxy object are forwarded to this instance * I am not sure why this is needed, given ghost objects. * I understand that you want this for when instantiation is delegated to a third party (in Symfony's DIC, a factory), but I feel like the ghost design is so much more fool-proof than the proxy approach. * Perhaps worth taking another stab at sharing object identity, before implementing these? * another note on naming: I used "value holder" inside ProxyManager, because using just "proxy" as a name led to a lot of confusion. This also applies to me trying to distinguish proxy types inside the RFC and this discussion. > Internal objects are not supported because their state is usually not managed via regular properties. * sad, but understandable limitation * what happens if a class `extends` an internal engine class, like the gruesome `ArrayObject`? The API uses various flags given as options ```php public int const SKIP_INITIALIZATION_ON_SERIALIZE = 1; public int const SKIP_DESTRUCTOR = 2; public int const SKIP_INITIALIZED_READONLY = 4; public int const RESET_INITIALIZED_READONLY = 8; ``` * IMO, these should be `enum` types, given in as a `list` to the call site * bitmasks are really only relevant in serialization / storage contexts, IMO, for compressing space as much as possible * the bitmasks in the reflection API are already very confusing and hard to use, and I say that as someone that wrapped the entire reflection API various times, out of despair ```php public function newLazyGhost(callable $initializer, int $options = 0): object {} public function newLazyProxy(callable $factory, int $options = 0): object {} ``` * Given the recent improvements around closures and the `...` syntax ( https://wiki.php.net/rfc/first_class_callable_syntax), is it worth having `Closure` only as argument type? * should we declare generic types in the RFC/docs, even if just in the stubs? * they would serve as massive documentation improvement for Psalm and PHPStan * it would be helpful to document `$initializer` and `$factory` as `:void` or `:object` functions * can the engine check that, perhaps? I have no idea if `Closure` can provide such information on return types, inside the engine. ```php public function initialize(object $object, bool $skipInitializer = false): object {} ``` * worth dividing this into * `initialize()` * `markAsAlreadyInitialized()` * don't use a boolean flag for two functions that do different things * all methods were added to `ReflectionClass` * IMO worth having this as separate class/object where this is attached * if one needs to decorate/stub such API in userland, it is therefore a completely separate decoration * `ReflectionClass` is already gigantic * a smaller API surface that only does proxies (perhaps different based on proxy strategy) would be very beneficial * suggestion: something like `new GhostObject($className)` and `new Proxy($className)` * I understand that the interactions with `ReflectionClass#getProperty()` felt natural, but the use-case is narrow, and `ReflectionClass` is already really humongous > The `resetAsLazy*()` methods accept an already created instance. > This allows writing classes that manage their own laziness * overall understandable * a bit weird to support this, for now * useful for resettable interfaces: Symfony DIC could benefit from this * what happens if `ReflectionClass#reset*()` methods are used on a different class instance? * considered? ```php $reflector->getProperty('id')->skipLazyInitialization($post); ``` * perfect for partial objects / understanding why this was implemented * would it make sense to have an API to set "bulk" values in an object this way, instead of having to do this for each property manually? * avoids instantiating reflection properties / marking individual properties manually * perhaps future scope? * thinking `(new GhostObject($class))->initializePropertiesTo(['foo' => 'bar', 'baz' => 'tab'])` > Initialization Triggers * really happy to see all these edge cases being considered here! * how much of this new API has been tried against the test suite of (for example) `ocramius/proxy-manager`? * mostly asking because there's tons of edge cases noted in there > Cloning, unless `__clone()` is implemented and accesses a property. * how is the initializer of a cloned proxy used? * Is the initializer cloned too? * what about the object that a lazy proxy forwards state access to? Is it cloned too? > The following special cases do not trigger initialization of a lazy object: * Will accessing a property via a debugger (such as XDebug) trigger initialization here? * asking because debugging proxy initialization often led to problems, in the past * sometimes even IDEs crashing, or segfaults * this wording is a bit confusing: > Proxy Objects > The actual instance is set to the return value. * considering the following paragraph: > The proxy object is _not_ replaced or substituted for the actual instance. > After initialization, property accesses on the proxy are forwarded to the actual instance. > Observing properties of the proxy has the same result as observing properties of the actual instance. * This is some sort of "quantum locking" of both objects? * How hard is it to break this linkage? * Can properties be `unset()`, for example? * what happens to dynamic properties? * I don't use them myself, and I discourage their usage, but it would be OK to just document the expected behavior > The proxy and actual instance have distinct identities. * Given that we went great lengths to "quantum lock" two objects' properties, wasn't it perhaps feasible to replace the proxy instance? * I have no idea if that would be possible/wished * would require merging `spl_object_id()` within the scope of the initializer stack frame, with any outer ones * would solve any identity problems that still riddle the lazy proxy design (which I think is incomplete, right now) > The scope and `$this` of the initializer function is not changed * good / thoughtful design * using `__construct()` or reflection properties suffices for most users > If the initializer throws, the object properties are reverted to their pre-initialization state and the object is > marked as lazy again. * this is some sort of "transactional" behavior * welcome API, but is it worth having this complexity? * is there a performance tradeoff? * is a copy of the original state kept during initializer calls? * OK with it myself, just probing design considerations * the example uses `setRawValueWithoutLazyInitialization()`, and initialization then accesses `public` properties * shouldn't a property that now **has** a value not trigger initialization anymore? * or does that require `ReflectionProperty#skipLazyInitialization()` calls, for that to work? > `ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE`: By default, serializing a lazy object triggers its initialization > This flag disables that behavior, allowing lazy objects to be serialized as empty objects. * how would one deserialize an empty object into a proxy again? * would this understanding be deferred to the (de-)serializer of choice? * exercise for userland? > `ReflectionClass::newLazyProxy()` > The factory should return a new object: the actual instance. * what happens if the user mis-implements the factory as `function (object $proxy): object { return $proxy; }`? * this is obviously a mistake on their end, but is it somehow preventable? > The `resetAsLazyGhost()` method resets an existing object and marks it as lazy. > The indented use-case is for an object to manage its own lazyness by calling the method in its constructor. * this certainly makes it easier to design "out of the box" lazy objects * perhaps more useful for tools like ORMs, (de-)serializers and DICs though * using the proxy API internally in classes like DB connections feels a bit overkill, to me > `ReflectionClass::SKIP_INITIALIZED_READONLY` > If this flag is set, these properties are skipped and no exception is thrown. > The behavior around readonly properties is explained in more details later. > `ReflectionClass::RESET_INITIALIZED_READONLY` * while I can see this as useful, it effectively completely breaks the `readonly` design * this is something I'd probably vote against: not worth breaking `readonly` for just the `reset*()` API here * `reset*()` is already a niche API inside the (relatively) niche use-case of laziness: I wouldn't bypass `readonly` for it * `readonly` provided developer value is bigger than lazy object value, in my mind > `ReflectionClass::resetAsLazyProxy()` > The proxy and the actual instance are distinct objects, with distinct identities. * When creating a lazy proxy, all property accesses are forwarded to a new instance * are all property accesses re-bound to the new instance? * are there any leftovers pointing to the old instance anywhere? * thinking dynamic properties and similar > If `$skipInitializer` is true, the behavior is the one described for Ghost Objects in the Initialization Sequence > section, except that the initializer is not called. * please make a separate method for this * it's not worth cramming a completely different behavior in the same method * can document the methods completely independently, this way * can deprecate the new method separately, if a design flaw is found in future > ReflectionProperty::setRawValueWithoutLazyInitialization() > The method does not call hooks, if any, when setting the property value. * So far, it has been possible to `unset($object->property)` to force `__get` and `__set` to be called * will `setRawValueWithoutLazyInitialization` skip also this "unset properties" behavior that is possible in userland? * this is fine, just a documentation detail to note * if it is like that, is it worth renaming the method `setValueWithoutCallingHooks` or such? * not important, just noting this opportunity > Readonly properties * while I appreciate the effort into digging in `readonly` properties, this section feels extremely tricky * I would suggest not implementing (for now) either of * `SKIP_INITIALIZED_READONLY` * `RESET_INITIALIZED_READONLY` * I would suggest leaving some time for these, and re-evaluating after the RFC lands / starts being used > Destructors > The destructor of proxy objects is never called. We rely on the destructor of the proxied instance instead. * raising an edge case here: `spl_object_*` and object identity checks may be used inside a destructor * for example, a DB connection de-registering itself from a connection pool somewhere, such as `$pool->deRegister($this)` * the connection pool may have the `spl_object_id()` of the proxy, not the real instance * this is not a blocker, just an edge case that may require documentation * it reconnects with "can we replace the object in-place?" question above: replacing objects worth exploring > It employs the ghost strategy by default unless the dependency is to be instantiated > and initialized by a user-provided factory * one question arises here: can this RFC create proxies of **interfaces** at all? * if not, does it throw appropriate exceptions? * the reason this question comes up is that, especially in the context of DICs, factories are for interfaces * concrete classes are implementation details of factories, usually * very difficult to use lazy proxy and ghost object design with services that decorate each other * this is semi-discussed in "About Proxies" below, around "inheritance-proxy" * still worth mentioning "no interfaces" as a clear limitation, perhaps? Marco Pivetta https://mastodon.social/@ocramius https://ocramius.github.io/ --00000000000082c900061b96ab74 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hey Nicolas, Arnaud,

=
On Tue, 4 = Jun 2024 at 14:29, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
=

Please find all the details here:
https://wiki.php.net/rf= c/lazy-objects


Fir= st of all, let me say that this is a fantastic RFC: having maintained both = mine and Doctrine's version of lazy proxies for many years, this is ind= eed a push in the right direction, making laziness an engine detail, rather= than a complex userland topic with hacks.

Moving = this forward will allow us (in a far future) to get rid of some BC boundari= es around the weird `unset()` semantics of properties, which were indeed pr= oblematic with typed properties and `readonly` properties.

Stellar work: well done!

## TL;DR: of= my feedback:

=C2=A0* RFC is good / useful / neede= d: will vote for it
=C2=A0* ghost proxies are well designed /= fantastic feature
=C2=A0* lazy proxies should probably explore r= eplacing object identity further
=C2=A0* don't like expan= ding `ReflectionClass` further: `LazyGhost` and `LazyProxy` classes (or suc= h) instead
=C2=A0* `initialize()` shouldn't have a boolean be= havioral switch parameter: make 2 methods
=C2=A0* flags should be= a `list<SomeEnumAroundProxies>` instead. A bitmask for a new API fee= ls unsafe and anachronistic, given the tiny performance hit.
= =C2=A0* don't touch `readonly` because of lazy objects: this feature is= too niche to cripple a major-major feature like `readonly`. I would sugges= t deferring until after the first bits of this RFC landed.
That said, I took some notes that I'd like you to both con= sider / answer.

## Raw feedback

I did skim through the thread, but did not read it a= ll, so please excuse me if some feedback is potentially duplicate.
Notes are succinct/to the point: I abuse bullet points heavily, sorry :-)=

> From an abstraction point of view, lazy objects f= rom this RFC are indistinguishable from non-lazy ones

* do the following aspects always apply? I understand they don'= t for lazy proxies, just for ghosts?
=C2=A0=C2=A0 * `spl_obje= ct_id($object) =3D=3D=3D spl_object_id($proxy)`?
=C2=A0=C2=A0 * `get_cla= ss($object) =3D=3D=3D get_class($proxy)`?

> Execution of methods = or property hooks does not trigger initialization until one of them accesse= s a backed property.

* excellent! The entire design revolving around= object state makes it so much easier to reason about the entire behavior t= oo!

> Proxies: The initializer returns a new instance, and intera= ctions with the proxy object are forwarded to this instance

* I am n= ot sure why this is needed, given ghost objects.
=C2=A0=C2=A0 * I= understand that you want this for when instantiation is delegated to a thi= rd party (in Symfony's DIC, a factory), but I feel like the ghost desig= n is so much more fool-proof than the proxy approach.
=C2=A0=C2= =A0 * Perhaps worth taking another stab at sharing object identity, before = implementing these?
* another note on naming: I used "value = holder" inside ProxyManager, because using just "proxy" as a= name led to a lot of confusion. This also applies to me trying to distingu= ish proxy types inside the RFC and this discussion.

> Internal objects are not supported because their state is usuall= y not managed via regular properties.

* sad, but understandable limi= tation
* what happens if a class `extends` an internal engine cla= ss, like the gruesome `ArrayObject`?

The API uses = various flags given as options

```php
=C2=A0 =C2=A0 public int co= nst SKIP_INITIALIZATION_ON_SERIALIZE =3D 1;
=C2=A0 =C2=A0 public int con= st SKIP_DESTRUCTOR =3D 2;
=C2=A0 =C2=A0 public int const SKIP_INITIALIZE= D_READONLY =3D 4;
=C2=A0 =C2=A0 public int const RESET_INITIALIZED_READO= NLY =3D 8;
```

* IMO, these should be `enum` types, given in as a= `list<TOption>` to the call site
=C2=A0 =C2=A0 * bitmasks are rea= lly only relevant in serialization / storage contexts, IMO, for compressing= space as much as possible
=C2=A0=C2=A0=C2=A0 * the bitmasks in t= he reflection API are already very confusing and hard to use, and I say tha= t as someone that wrapped the entire reflection API various times, out of d= espair

```php
public function newLazyGhost(= callable $initializer, int $options =3D 0): object {}
public function ne= wLazyProxy(callable $factory, int $options =3D 0): object {}
```

= * Given the recent improvements around closures and the `...` syntax (https://wiki.ph= p.net/rfc/first_class_callable_syntax), is it worth having `Closure` on= ly as argument type?
* should we declare generic types in the RFC/docs, = even if just in the stubs?
=C2=A0 =C2=A0 * they would serve as massive d= ocumentation improvement for Psalm and PHPStan
=C2=A0 =C2=A0 * it would = be helpful to document `$initializer` and `$factory` as `:void` or `:object= ` functions
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * can the engine check that, per= haps? I have no idea if `Closure` can provide such information on return ty= pes, inside the engine.

```php
public function initialize(object = $object, bool $skipInitializer =3D false): object {}
```

* worth = dividing this into
=C2=A0 =C2=A0 * `initialize()`
=C2=A0 =C2=A0 * `ma= rkAsAlreadyInitialized()`
=C2=A0 =C2=A0 * don't use a boolean flag f= or two functions that do different things

* all methods were added t= o `ReflectionClass`
=C2=A0 =C2=A0 * IMO worth having this as separate cl= ass/object where this is attached
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * if one n= eeds to decorate/stub such API in userland, it is therefore a completely se= parate decoration
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 * `Reflectio= nClass` is already gigantic
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 * a smaller API surface that only does proxi= es (perhaps different based on proxy strategy) would be very beneficial
=C2=A0=C2=A0 * suggestion: something like `new GhostObject($classNam= e)` and `new Proxy($className)`
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 * = I understand that the interactions with `ReflectionClass#getProperty()` fel= t natural, but the use-case is narrow, and `ReflectionClass` is already rea= lly humongous

> The `resetAsLazy*()` methods accept an already cr= eated instance.
> This allows writing classes that manage their own l= aziness

* overall understandable
=C2=A0 =C2=A0 * a bit weird to s= upport this, for now
=C2=A0 =C2=A0 * useful for resettable interfaces: S= ymfony DIC could benefit from this
* what happens if `ReflectionClass#re= set*()` methods are used on a different class instance?
=C2=A0 =C2=A0 * = considered?

```php
$reflector->getProperty('id')->s= kipLazyInitialization($post);
```

* perfect for partial objects /= understanding why this was implemented
=C2=A0 =C2=A0 * would it make se= nse to have an API to set "bulk" values in an object this way, in= stead of having to do this for each property manually?
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 * avoids instantiating reflection properties / marking individua= l properties manually
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * perhaps future scope= ?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=C2=A0 * thinking `(new Ghost= Object($class))->initializePropertiesTo(['foo' =3D> 'bar&= #39;, 'baz' =3D> 'tab'])`

> Initial= ization Triggers

* really happy to see all these edge cases being co= nsidered here!
=C2=A0 =C2=A0 * how much of this new API has been tried a= gainst the test suite of (for example) `ocramius/proxy-manager`?
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 * mostly asking because there's tons of edge cases= noted in there

> Cloning, unless `__clone()` is implemented and = accesses a property.

* how is the initializer of a cloned proxy used= ?
=C2=A0 =C2=A0 * Is the initializer cloned too?
=C2=A0=C2=A0= =C2=A0 * what about the object that a lazy proxy forwards state access to? = Is it cloned too?

> The following special c= ases do not trigger initialization of a lazy object:

* Will accessin= g a property via a debugger (such as XDebug) trigger initialization here?=C2=A0 =C2=A0 * asking because debugging proxy initialization often led t= o problems, in the past
=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 * sometim= es even IDEs crashing, or segfaults

* this wording= is a bit confusing:

> Proxy Objects
> Th= e actual instance is set to the return value.

* considering the foll= owing paragraph:

> The proxy object is _not_ replaced or s= ubstituted for the actual instance.


> After= initialization, property accesses on the proxy are forwarded to the actual= instance.
> Observing properties of the proxy has the same result as= observing properties of the actual instance.

* This is some sort of= "quantum locking" of both objects?
* How hard is it to break = this linkage?
=C2=A0 =C2=A0 * Can properties be `unset()`, for example?<= br>=C2=A0 =C2=A0 * what happens to dynamic properties?
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 * I don't use them myself, and I discourage their usage, but= it would be OK to just document the expected behavior

> The prox= y and actual instance have distinct identities.

* Given that we went= great lengths to "quantum lock" two objects' properties, was= n't it perhaps feasible to replace the
=C2=A0 proxy instance?
=C2= =A0 =C2=A0 * I have no idea if that would be possible/wished
=C2=A0 =C2= =A0 * would require merging `spl_object_id()` within the scope of the initi= alizer stack frame, with any outer ones
=C2=A0=C2=A0=C2=A0 * woul= d solve any identity problems that still riddle the lazy proxy design (whic= h I think is incomplete, right now)

> The scope= and `$this` of the initializer function is not changed

* good / tho= ughtful design
=C2=A0 =C2=A0 * using `__construct()` or reflection prope= rties suffices for most users

> If the initializer throws, the ob= ject properties are reverted to their pre-initialization state and the obje= ct is
> marked as lazy again.

* this is some sort of "tra= nsactional" behavior
=C2=A0 =C2=A0 * welcome API, but is it worth h= aving this complexity?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * is there a performa= nce tradeoff?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * is a copy of the original st= ate kept during initializer calls?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * OK with= it myself, just probing design considerations
* the example uses `setRa= wValueWithoutLazyInitialization()`, and initialization then accesses `publi= c` properties
=C2=A0 =C2=A0 * shouldn't a property that now **has** = a value not trigger initialization anymore?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 = * or does that require `ReflectionProperty#skipLazyInitialization()` calls,= for that to work?

> `ReflectionClass::SKIP_INITIALIZATION_ON_SER= IALIZE`: By default, serializing a lazy object triggers its initialization<= br>> This flag disables that behavior, allowing lazy objects to be seria= lized as empty objects.

* how would one deserialize an empty object = into a proxy again?
=C2=A0 =C2=A0 * would this understanding be deferred= to the (de-)serializer of choice?
=C2=A0=C2=A0=C2=A0 * exercise = for userland?

> `ReflectionClass::newLazyProxy(= )`
> The factory should return a new object: the actual instance.
=
* what happens if the user mis-implements the factory as `function (obj= ect $proxy): object { return $proxy; }`?
=C2=A0 =C2=A0 * this is obvious= ly a mistake on their end, but is it somehow preventable?

> The `= resetAsLazyGhost()` method resets an existing object and marks it as lazy.<= br>> The indented use-case is for an object to manage its own lazyness b= y calling the method in its constructor.

* this certainly makes it e= asier to design "out of the box" lazy objects
=C2=A0 =C2=A0 * = perhaps more useful for tools like ORMs, (de-)serializers and DICs though=C2=A0 =C2=A0 * using the proxy API internally in classes like DB connect= ions feels a bit overkill, to me

> `ReflectionClass::SKIP_INITIAL= IZED_READONLY`
> If this flag is set, these properties are skipped an= d no exception is thrown.
> The behavior around readonly properties i= s explained in more details later.
> `ReflectionClass::RESET_INITIALI= ZED_READONLY`

* while I can see this as useful, it effectively compl= etely breaks the `readonly` design
=C2=A0 =C2=A0 * this is something I&#= 39;d probably vote against: not worth breaking `readonly` for just the `res= et*()` API here
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 * `reset*()`= is already a niche API inside the (relatively) niche use-case of laziness:= I wouldn't bypass `readonly` for it
=C2=A0 =C2=A0 * `rea= donly` provided developer value is bigger than lazy object value, in my min= d

> `ReflectionClass::resetAsLazyProxy()`
> The proxy and t= he actual instance are distinct objects, with distinct identities.

*= When creating a lazy proxy, all property accesses are forwarded to a new i= nstance
=C2=A0 =C2=A0 * are all property accesses re-bound to the new in= stance?
=C2=A0 =C2=A0 * are there any leftovers pointing to the old inst= ance anywhere?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * thinking dynamic properties= and similar

> If `$skipInitializer` is true, the behavior is the= one described for Ghost Objects in the Initialization Sequence
> sec= tion, except that the initializer is not called.

* please make a sep= arate method for this
=C2=A0 =C2=A0 * it's not worth cramming a comp= letely different behavior in the same method
=C2=A0 =C2=A0 * can documen= t the methods completely independently, this way
=C2=A0 =C2=A0 * can dep= recate the new method separately, if a design flaw is found in future
> ReflectionProperty::setRawValueWithoutLazyInitialization()
> T= he method does not call hooks, if any, when setting the property value.
=
* So far, it has been possible to `unset($object->property)` to forc= e `__get` and `__set` to be called
=C2=A0 =C2=A0 * will `setRawValueWith= outLazyInitialization` skip also this "unset properties" behavior= that is possible in userland?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * this is fin= e, just a documentation detail to note
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * if = it is like that, is it worth renaming the method `setValueWithoutCallingHoo= ks` or such?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 * not important, = just noting this opportunity

> Readonly properties

* while= I appreciate the effort into digging in `readonly` properties, this sectio= n feels extremely tricky
=C2=A0 =C2=A0 * I would suggest not implementin= g (for now) either of
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * `SKIP_INITIALIZED_RE= ADONLY`
=C2=A0 =C2=A0 =C2=A0 =C2=A0 * `RESET_INITIALIZED_READONLY`
= =C2=A0 =C2=A0 * I would suggest leaving some time for these, and re-evaluat= ing after the RFC lands / starts being used

> Destructors
>= The destructor of proxy objects is never called. We rely on the destructor= of the proxied instance instead.

* raising an edge case here: `spl_= object_*` and object identity checks may be used inside a destructor
=C2= =A0 =C2=A0 * for example, a DB connection de-registering itself from a conn= ection pool somewhere, such as `$pool->deRegister($this)`
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 * the connection pool may have the `spl_object_id()` of t= he proxy, not the real instance
=C2=A0 =C2=A0 * this is not a blocker, j= ust an edge case that may require documentation
=C2=A0 =C2=A0 =C2=A0 =C2= =A0 * it reconnects with "can we replace the object in-place?" qu= estion above: replacing objects worth exploring

> It employs the = ghost strategy by default unless the dependency is to be instantiated
&g= t; and initialized by a user-provided factory

* one question arises = here: can this RFC create proxies of **interfaces** at all?
=C2= =A0=C2=A0=C2=A0 * if not, does it throw appropriate exceptions?
=C2=A0 =C2=A0 * the reason this question comes up is that, especially in= the context of DICs, factories are for interfaces
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 * concrete classes are implementation details of factories, usually<= /div>
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 * ve= ry difficult to use lazy proxy and ghost object design with services that d= ecorate each other
=C2=A0 =C2=A0 * this is semi-discussed in = "About Proxies" below, around "inheritance-proxy"
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 * still worth mentioning "no interfaces&qu= ot; as a clear limitation, perhaps?



=C2=A0
--00000000000082c900061b96ab74--