On Sat, Jun 15, 2024, at 8:28 AM, Arnaud Le Blanc wrote:
> Hi Larry,
>

>> Under Common Behavior, you have an example of calling the constructor 
>> directly, using the reflection API, but not of binding the callable, which 
>> the text says is also available.  Please include an example of that so we 
>> can evaluate how clumsy (or not) it would be.
>
> I've clarified that binding can be achieved with Closure::bind(). 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 
> constructor
> - 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 = function() use ($c) {
  $this->a = $c->get(ServiceA::class);
  $this->b = $c->get(ServiceB::class);
}

$service = 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?

> 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 difference 
either way, correct?

>> 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:
>>
>> class Service {
>>     public function __construct(private ServiceA $a, private ServiceB $b) {}
>> }
>>
>> $c = some_container();
>>
>> $init = fn() => $this->__construct($c->get(ServiceA::class), 
>> $c->get(ServiceB::class));
>>
>> $service = new ReflectionLazyObjectFactory(Service::class, $init);
>>
>> (Most likely in generated code that can dynamically sort out the container 
>> calls to inline.)
>>
>> Am I missing something?
>
> No you are right, but they must fallback to the proxy strategy when
> the user provides a factory.
>
> E.g. this will use the ghost strategy because the DIC
> instantiates/initializes the service itself:
>
> my_service:
>     class: MyClass
>     arguments: [@service_a, @service_b]
>     lazy: true
>
> But this will use the proxy strategy because the DIC doesn't
> instantiate/initialize the service itself:
>
> my_service:
>     class: MyClass
>     arguments: [@service_a, @service_b]
>     factory: [@my_service_factory, createService]
>     lazy: true
>
> 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 called 
$factory?  Since that's basically what it's doing, and I'm unclear what it 
would do with the proxy object itself that's passed in.

>> 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 the 
>> "dumping ground" fo ReflectionClass, but honestly, that feels more ergonomic 
>> 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 
>> ReflectionObject, 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 looked at the 
reflection implementation in, well, ever. :-)

If it has to be a separate object, please don't make it extend ReflectionClass 
but still give it useful dynamic methods rather than static methods.  Or 
perhaps even do something like

$ghost = new ReflectionGhostInstance(SomeClass::class, $init);
$proxy  = new ReflectionProxyINstance(SOmeClass::class, $init);

And be done with it.  (I'm just spitballing here.  As I said, I like the 
feature, I just want to ensure the ergonomics are as good as possible.)

--Larry Garfield

Reply via email to