On Sat, Mar 9, 2024, at 11:42 AM, Rasmus Schultz wrote: > Hey Larry, > > Per chance, have you run into discussions elsewhere about this > alternative interface? > > service-provider/src/ServiceProviderInterface.php at 0.5.0 · > mindplay-dk/service-provider · GitHub > <https://github.com/mindplay-dk/service-provider/blob/0.5.0/src/ServiceProviderInterface.php> > > It's a refactored version of the previously proposed interface, > designed to work better for compiled containers. > > It avoids the use of callables, and the need to registration at run-time. > > A compiled container can retrieve the list of services/extensions at > compile-time, resolve service overrides among providers, etc. avoiding > this overhead at run-time. > > In practice, this would reduce the run-time overhead for compiled > containers (versus their own internal providers) to a single > function-call per service/extension. > > So it's not zero overhead, but it is reasonably close - perhaps as > close as you can get, and considerably less overhead compared with the > previous proposal. > > A hand-written or compiled service provider can use the match-based > approach, as you illustrated above. > > In addition, a compiled container/builder can make a generated provider > available, retaining the full performance of the compiled factory, if > used to provide services to other (possibly non compiled) containers. > > I mean, short of creating a standard that is fully declarative (e.g. an > XML schema) I believe this is the best we can expect in terms of > performance, isn't it? If it involves an interface, you can't really > reduce the overhead to less than a function call. > > Whether a PSR in this space ever happens, is essentially up to you, > Larry. No other core members have shown any interest in this, and > realistically, it's not going to go any further (and no one is going to > take this seriously or bother participating) without a formal working > group and a PSR number. > > I would love to see this come to fruition - but unless a core member > steps up to support this, I should probably just give up and move on > myself. > > What do you think, is there any chance this will ever happen, or am I > just spinning my wheels here? :-) > > Regards, > Rasmus Schultz
Well, we'd need more than just a CC member to sponsor it. We'd also need at least 3 other people for a working group, preferably from projects that publish popular DICs (Symfony, PHP-DI, etc.) Korvin and I talked a little in Discord. To summarize my position, and the challenge I see: There's a couple of standard features of DI Containers these days, which if we're going to standardize registration at all need to be fully considered. However, some of them are at odds with each other. 1. Compiled containers. These should be as fast as possible. The gold standard right now (not all do this, but the good ones do) is to go all the way down to something like this: class CompiledContainer { private array $services = []; public function get(string $id): mixed { return $this->services[$id] ??= return match($id) { ClassA::class => new ClassA(), ClassB::class => new ClassB($this->get(ClassA::class), InterfaceC::class => $this->get(ClassB::class), // ... } } } Yes there's cases where you may also need additional method calls for setup, but those can be handled. This is just an example, but a design that allows this to be produced is, IMO, mandatory. 2. Runtime configured containers. These are also common (even in cases where they shouldn't be, IMO, like Laravel). For many lower-traffic use cases they're much easier to work with, and fast enough. The quintessential example here is Pimple, which shows just how trivially easy this can be if you don't need much functionality. 3. Complex/contextual creation. This is usually handled by a factory method, generally another service. This is also an important use case, even if not the typical one. 4. Peer-manipulation. (This is a bad name, please come up with a better one.) In some cases, one dependency wants to manipulate the DI configuration of another dependency. This could be to add a method call to the setup routine, or to build up an array of other services to use as a constructor dependency, etc. Symfony solves this with Compiler Passes, and in practice I don't think there's a better option, give or take implementation details. Here's the problem: For compiled containers to work properly, you really have to use an AST of container definitions that can then be compiled down into the appropriate code, and you cannot have any closures or instantiated objects in the process. Without that, you're adding at absolute minimum an extra function call to every lookup, which adds up when there's hundreds of services. Potentially much more if you're doing any kind of complex/contextual creation or peer-manipulation. The AST approach is basically what Symfony does today, although I find their interface and data model for it rather clumsy. (Due largely to it being defined in PHP 5.3, when the language was a lot weaker.) However, an AST approach is necessarily more complex, and while it could be implemented by a runtime container it would likely be slower than just passing everything a factory closure and moving on. An AST approach does not necessarily mean YAML or XML files. The smart way to do file-based configuration would be to have the config files translate to the AST, so that it can be unified with any code-provided definitions. A config file would be just another peer-manipulation. (Which is, yes, exactly how Symfony works. I keep coming back to it being the right architectural design with a lackluster API.) I don't know how to resolve this tension between compiled and runtime containers. But here's why it matters: If a PSR standard puts forth a runtime-optimized registration design, one based on factories, then any libraries that use it to expose their dependencies necessarily are hamstrung in a compiled container situation. That's not good. OTOH, if we put forward a compile-optimized design, it makes supporting runtime containers more difficult and slower. And we should assume that the majority of services eventually get registered via whatever we come up with, so "it's just for a few services, it won't make a difference" doesn't really fly. (If it's only used in a few edge cases, then we have failed with the PSR.) Rasmus' latest, if I understand it, is somewhat better, as it essentially tells each provider to have its own match() block or equivalent. That's not as bad as having the provider return an array of factories, but it still adds at least one method call to every lookup, and if extensions are used (I don't fully understand those), it's several more. We don't need to resolve these issues right now. We need to agree that they need to be solved, and find people interested in solving them. As a reminder, the purpose of a Working Group is to have these discussions in a focused way with the experts in a given field, not to throw it out to a list of however many people, most of whom are idle. :-) All we need for a WG is a statement of purpose and people willing to work on it. The latter is the challenge for now. Wearing my Core Committee hat, I would vote Yes on a service registration working group, if the interested parties can be assembled. I would vote no on a final result that doesn't adequately address the tension I outline above. What that solution is, well, that's up for the working group to figure out. So the task for now isn't figuring out the solution, it's assembling a working group. --Larry Garfield -- You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group. To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/e43c1a77-f2b2-4be8-acdc-a9119b42f973%40app.fastmail.com.