Re: [RFC] Lazy Objects

From: Date: Thu, 18 Jul 2024 07:07:44 +0000
Subject: Re: [RFC] Lazy Objects
References: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message
On Wed, Jul 17, 2024, at 20:31, Nicolas Grekas wrote:
> Dear all,
> 
> Le mar. 16 juil. 2024 à 17:51, Nicolas Grekas <[email protected] <mailto:nicolas.grekas%[email protected]>> a
> écrit :
>> Hi there,
>> 
>> Le mar. 16 juil. 2024 à 10:13, Nicolas Grekas <[email protected] <mailto:nicolas.grekas%[email protected]>> a
>> écrit :
>>> 
>>> 
>>> Le lun. 15 juil. 2024 à 21:42, Tim Düsterhus <[email protected]> a écrit :
>>>> Hi
>>>> 
>>>> On 7/15/24 09:25, Nicolas Grekas wrote:
>>>> > Testing is actually a good domain where resetting lazy objects might open
>>>> > interesting use cases.
>>>> > This reminded me about zenstruck/foundry, which leverages the
>>>> > LazyProxyTrait to provide refreshable fixture objects
>>>> > <https://symfony.com/bundles/ZenstruckFoundryBundle/current/index..html#auto-refresh>
>>>> > and provides nice DX thanks to this capability.
>>>> > 
>>>> 
>>>> I have not used this library before, but I have taken a (very) brief 
>>>> look at the code and documentation.
>>>> 
>>>> My understanding is that all the fixture objects are generated by a 
>>>> corresponding Factory class. This factory clearly has the capability of 
>>>> constructing objects by itself, so it could just create a lazy proxy 
>>>> instead?
>>>> 
>>>> I'm seeing the instantiateWith() example in the documentation
>>>> where 
>>>> the user can return a constructed object themselves, but I'm not seeing 
>>>> how that can safely be combined with the reset*() methods: Anything 
>>>> special the user did to construct the object would be reverted, so the 
>>>> user might as well rely on the default construction logic of the factory 
>>>> then.
>>>> 
>>>> What am I missing?
>>> 
>>> Finding the spot where the reset method would be useful is not easy. Here it is:
>>> https://github.com/zenstruck/foundry/blob/v2.0.7/src/Persistence/IsProxy.php#L66-L76
>>> 
>>> Basically, the reset method is not needed when creating the lazy proxy. But it's
>>> needed to refresh it when calling $object->_refresh(). The implementation I just linked swaps the
>>> real object bound to the proxy for another one (the line
>>> "Configuration::instance()->persistence()->refresh($object);" swaps by reference).
>> 
>> 
>> After chatting a bit with Benjamin on Slack, I realized that the sentence "The
>> indented use-case is for an object to manage its own laziness by calling the method in its
>> constructor" was a bit restrictive and that there are more use cases for reset methods.
>> 
>> Here is the revised part about resetAsLazyGhost in the RFC:
>> 
>> This method allows an object to manage its own laziness by calling the method in its
>> constructor, as demonstrated here <https://gist.github.com/arnaud-lb/9d52e2ba4e278411bff3addf75ce56be>.
>> In such cases, the proposed lazy-object API can be used to achieve lazy initialization at the
>> implementation detail level. 
>> 
>> Another use case for this method is to achieve resettable services. In these scenarios, a
>> service object already inserted into a complex dependency graph can be reset to its initial state
>> using the lazy object infrastructure, without its implementation being aware of this concern. A
>> concrete example of this use case is the Doctrine EntityManager, which can end up in a hard to
>> recover <https://github.com/doctrine/orm/issues/5933>
>> "closed" state, preventing its use in long-running processes. However, thanks to the
>> lazy-loading code infrastructure <https://github.com/symfony/symfony/blob/1a16ebc32598faada074e0af12a6a698d2964a5e/src/Symfony/Bridge/Doctrine/ManagerRegistry.php#L42>,
>> recovering from such a state is possible. This method would be instrumental in achieving this
>> capability without resorting to the current complex code used in userland.
>> 
>> I hope this helps.
> 
> A bit unrelated to the above topic: we've further clarified the RFC by addition
> restrictions to what can be done with lazy proxies. Namely, when the factory returns an object from
> a parent class, we describe that adding more on the proxy class would throw, and we also explain
> why. We also added a restriction to prevent a proxy from having an overridden __clone or __destruct
> when the factory returns a parent, and explained why again.
> 
> This should simplify the overall behavior by preventing edge case that wouldn't have easy
> answers. If those limitations prove too restrictive in practice (my experience tells me they should
> be fine), they could be leveraged in the future.
> 
> On our side, this should close the last topics we wanted to address before opening the vote.
> 
> Please let us know if anyone has other concerns.
> 
> Cheers,
> Nicolas

If you cannot use an instance of a subclass as the actual object, then it the methods probably
shouldn't exist on ReflectionClass since you use that to reflect on interfaces and abstract
classes. This also limits the usability, as for example, in a container, you might know that the
type you need to return is MyInterface, but not know what type the actual factory will return.

— Rob


Thread (95 messages)

« previous php.internals (#124463) next »