Just to entertain the idea, if we were to go fully declarative, service providers cannot include any behavior (classes, methods, closures or interfaces) and must essentially be data structures, since nothing else works for the compiled containers - which would mean:
* Non-compiled containers would be unable to act as service providers. * Non-compiled containers would take on extra overhead from parsing the data-structures. * Service providers would need to be hand-written, with no IDE support or static inspections. (beyond checking that a class-name or service ID is a string etc.) * I don't think most people would want to write service providers under these circumstances? But maybe that's okay? Maybe they're only required for a few libraries with complex bootstrapping, and maybe there is still value in making this bootstrapping portable between containers. I'm not optimistic about it, to be honest, but I am willing to entertain the idea. Just to get some sense of what we're talking about, I wrote a quick draft: https://gist.github.com/mindplay-dk/6118034336e32376c62c6ca5f28b9470 It's a far cry from the simplicity of the current proposal - you basically have to replace at least a subset of the programming language with a value-based model, essentially a DSL or AST of a sort. It's not far from what you have to do with the DSLs of existing compiled containers though, most of which also do not provide IDE support or static type-checking. One glaring issue with this approach is it implies that callback-based containers would be able to somehow reverse callables back to models, which is near-impossible to implement - either that or just accept the limitation that standard service providers can only extend standard service providers, a pretty harsh tradeoff for runtime configured containers that really takes away from the value of a standard. I don't know if this is worth pursuing. I'm not convinced something like this is feasible without both sides of the ecosystem making some tradeoffs - if the compiled side of the ecosystem isn't willing to give an inch on performance, essentially we're talking about a PSR that only works for compiled containers, aren't we? Rasmus Schultz On Friday, March 22, 2024 at 4:14:43 PM UTC+1 Larry Garfield wrote: > 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/716b7c7c-b43d-4773-b809-669ecc920e5dn%40googlegroups.com.