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