On Sat, Mar 23, 2024, at 4:33 AM, Rasmus Schultz wrote: > Thanks for your reply, and thanks for laying it out in such detail. > > I understand performance being a feature of the compiled containers - > but in the same way, simplicity is a feature of runtime configured > containers. > > As you said, these requirements are at odds with each other - they > mandate something complex, which won't make sense in the context of > containers where simplicity is a feature. And to be fair, the current > proposals mandate something simple, which won't perform to the > standards of compiled containers. > > Let's break this down and simplify. Your programming language has two > broad categories of features: data and behavior - data meaning strings, > numbers, attributes, data structures, etc. and behavior meaning > classes, functions, closures, interfaces, etc. > > Compiled containers require data. It doesn't matter how that data is > defined - using a PHP DSL, YAML, XML, JSON, whatever. > > Runtime configured containers require behavior - or maybe some mix of > data and behavior, but it always involves behavior, in some form. > > So when it comes down to it, service providers must either consist of > data only, or some mix of data and behavior. > > If they rely on any sort of behavior (classes, functions, closures) > this inherently introduces runtime overhead for the compiled containers. > > Given the constraints you laid out, where no performance tradeoff is > acceptable, that means nothing involving behavior would be acceptable. > > This leaves data-only approaches as the only option on the table. > > Now, regardless of how you define or source that data, this has some > implications. > > First, this standard would offer no IDE support or static validation > when writing standard providers by hand. At least, nothing beyond > validating that a class-name a service-name is a string. An IDE or > static analysis tool can only validate code involving behaviors - > calling constructors, checking argument types, etc. all involves > behavior. > > Second, this standard would prevent runtime configured containers from > acting as service providers - no one could build a service provider > factory that relies on behavior in any form, except maybe by > decompiling and transforming source cod, but this would turn your > runtime configured container into a compiled container, breaking the > "simplicity as a feature" requirement. > > And maybe we say, okay, that's an acceptable limitation - this PSR is > about providing a way to write standard service providers, we don't > care if containers can act as service providers, we don't care if > someone can build a service provider factory. All standard providers > will be written by hand or generated by a compiler.
I think there's at least two loopholes to exploit here: Nothing prevents a compiled container from using factories, and in practice most do. And one can build behavior off of data structures, as long as the behavior pattern is known. To spitball, so bear with me if this is a bit buggy or basic, suppose we had a service definition like so: class ServiceDef { /** @param array<string, DefRef> $constructorArgs */ public function __construct( public string $class, public array $constructorArgs = [], ) {} } Compiling that into a match statement is pretty straightforward: foreach ($def->constructorArgs as $name => $defRef) [ $args[] = sprintf('%s: $this->get(%s)' , $name, $defRef->name; } $arm = "new \$def->class(" . implode(', ', $args); // Append $arm into the match statement. However, very similar code can happen at runtime in a runtime container, if it has a list of service definitions objects. public function get(string $name): mixed { $class = $this->defs[$name]->class; foreach ($ $this->defs[$name]->constructorArgs as $name => $defRef) [ $args[$name] = $this->get($defRef->name); } return new $class(...$args); } It's a little more involved than just $this->factories[$name]($this), but not by that much. The performance should be in the same general neighborhood, if maybe slightly less. (Though if you're using a runtime container, you already don't care about performance optimization. Which is perfectly fine in some cases, but if you care about container performance, you're compiling it.) Conversely, for a compiled container, you can do this: class FactoryDef { public function __construct( public string $factory, public string $method = '__invoke', // And some kind of argument handling I'll skip for now to avoid typing, but it's largely the same as above. ) {} } For a runtime container, that's pretty easy, and barely any more than it would do with a closure today: public function get(string $name): mixed { $factory = $this->defs[$name]->factory; $factory = $this->defs[$name]->method; return $factory->$method(); } (Some kind of real polymorphism can probably make this all a lot smoother, but that's not the point right now.) For a compiled container, it could compile down to this: public function get(string $name): mixed { return $this->cache[$name] ??= match ($name) { AFactory::class => new SomeFactory(), AService::class => $this->get(AFactory::class)->theMethod(), } } All existing compiled containers I know of support something like this. At least the good ones. :-) Naturally the above is me just barfing ideas into an email, so it's not as robust as anything would need for production, but I think it makes the point that supporting both runtime and compiled containers is a solvable problem, just not a really trivial one. I will also note that no service registration PSR would preclude supporting some other mechanism. So if a given runtime implementation wanted to also support $container->factory(Foo::class, fn(ContainerInterface $c) => new FooBar()), it absolutely could without in any way contradicting the spec. > Well, then you still have the other limitations: when writing standard > providers you get no IDE support, no static validation, and the runtime > configured containers are required to parse and resolve this data and > call constructors dynamically, at runtime, adding more overhead. I disagree here. Everything in the examples above is well type-checked. The language itself is providing the validation we need, and any violation would be a type error already. At worst, we could throw on some PHPStan/Psalm-style generic docblocks to make hinting the return of get() better, which is... already an existing issue for PSR-11. :-) > If the position of the core committee is that no compromise would be > acceptable, in my opinion, the idea is dead. > > And in all honesty, maybe that's fine. If the community doesn't want > standard service providers, I can't make them want it. :-) To be clear: The Core Committee has taken no position on this matter. Two members of the core committee have expressed their personal views on the matter, and their reservations, but are both positive on the concept. The other 10 have been so far silent. But, and I hate to beat this dead horse but it's still wriggling, *the Core Committee is not the definition of "the community"*. If all 12 CC members agreed on a spec, but Symfony, PHP-DI, Laminas, and Laravel all completely ignored it, it would be a total failure. If Symfony, PHP-DI, Laminas, and Laravel all agreed on a common spec, and FIG ignored it, that would be a huge win for the ecosystem. FIG is here to help that second version happen, but its "blessing" means nothing unless implementers implement it. In short, stop talking to me and start talking to the Symfony DI, PHP-DI, Laminas DI, and Laravel DI maintainers. :-) Convincing the Core Committee to charter a working group will be, I can virtually guarantee, 100x easier than convincing enough of the right people to sign on to the working group in the first place. That's the blocker, not figuring out a perfect architecture for me. Also, other CC members: please weigh in here. Following this thread and weighing in with relevant, thoughtful comments is what you were elected for. Please do so. This should not be just me and Korvin. --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/01f1381f-42dd-4c9d-ac88-4813b9cf8db8%40app.fastmail.com.