On Wed, Jun 5, 2024, at 2:50 PM, Arnaud Le Blanc wrote:

> Good point. The Mutex constructor is called during "new Mutex()", but
> the object is made lazy after that, and the destructor is never
> called.
>
> We have made the following changes to the RFC:
>
> - makeLazyGhost / makeLazyProxy will call the object destructor
> - A new option flag is added, `ReflectionLazyObject::SKIP_DESTRUCTOR`,
> that disables this behavior
>
> This is not ideal since the intended use of these methods is to call
> them on objects created with newInstanceWithoutConstructor(), or
> directly in a constructor, and both of these will need this flag, but
> at least it's safe by default.
>
> Thanks again for the feedback.
>
> Best Regards,
> Arnaud

Let me make sure I am following, since I'm still having a hard time with the 
explanation in the RFC (as I discussed with Nicolas off list).

The two use cases intended here are, essentially, "delay invoking the 
constructor until first use" (ghost) and "sneak a factory object in the way 
that is called on first use" (virtual).  Right?

The ghost initializer callback is basically an alternate constructor (which 
will probably just call the real constructor in the typical case), and the 
virtual initializer callback is basically the body of the factory object.

So the most common ghost implementation for a service would be something like:

$c = the_container();

$object = new ReflectionClass(Foo::class)->newInstanceWithoutConstructor();

$initializer = static function(Foo $foo) => $foo->__construct($c->dep1, 
$c->dep2, $c->dep3);

$lazyReflector = ReflectionLazyObject::makeLazyGhost($object, $initializer, 
ReflectionLazyObject::SKIP_DESTRUCTOR);

Am I following?  Because if so, the proposed API is extremely clunky.  For one 
thing, using an input/output parameter ($object) is a code smell 99% of the 
time.  It's changing an un-constructed object into a ghost object, and 
returning... um, I'm not sure what.  It also means I need to use both 
reflection classes in different ways to achieve the result.

It seems a much cleaner API would be something like:

$object = new 
ReflectionClass(Foo::class)->newInstanceWithLazyConstructor($initializer);

In which case it becomes a lot more obvious that we are, essentially, "swapping 
out" the constructor for a lazy one.  It also suggests that perhaps the 
function should be using $this, not $foo, as it's running within the context of 
the object (I presume?  Can it call private methods?  I assume so since it can 
set private properties.)

That would also suggest this API for the other approach:

$initializer = static function() => new Foo($c->dep1, $c->dep2, $c->dep3);

$object = new 
ReflectionClass(Foo::class)->newInstanceWithLazyFactory($initializer);

In which case $object is the proxy, and gets "swapped out" for the return value 
of the $initializer on first use.

Am I understanding all this correctly?  Because if so, I think the above 
simplified API would make it much more obvious what is going on, much easier to 
work with, easier to document/explain, and simple enough that it could 
conceivably be used in cases outside of DI or ORMs, too.

If I'm way off and don't understand what you're doing, then please explain as 
I'm clearly very confused. :-)

--Larry Garfield

Reply via email to