Closed jakejohns closed 10 months ago
Hi, @jakejohns! Thanks for opening the issue!
If you do what you described in your example, you would depend on service names corresponding to class names, which is by far not always the case (and I would contend that it shouldn't be, but that's another topic). So, while it's perfectly possible to make a PSR-11 implementation that could work like that, it would not be standards-compliant, so you will only be able to use your own services (and other services that follow your convention, which there are unlikely to be many). If you need auto-wiring capabilities, I suggest you add them on top of standards-compliant implementations and definitions.
Thanks for the response, @XedinUnknown!
However, I’m not sure my question was clear, or perhaps I’m misunderstanding something, or perhaps I just disagree.
If you do what you described in your example, you would depend on service names corresponding to class names, which is by far not always the case (and I would contend that it shouldn't be, but that's another topic).
I apologize. It is true that the example above is written that way. However, the fact that the service name is the class name in the example is arbitrary to my point/purpose/question. I’m not proposing that this specification codify anything even remotely so rigorous (which would violate the “opaque identifier” of PSR-11). It was just an abstract example illustrating one potential use case of such functionality.
All that I’m proposing is that the full context (the container, as well as the name of the service being requested) be available to the factory. The above example could easily be written as follows, with no implication that the service name is a class name.
class FooFactory
{
public function __invoke(ContainerInterface $container, string $name) : AbstractFoo
{
$bar = $container->get('my-bar');
if ('my-baz' == $name) {
return new Baz($bar);
}
if ('my-bing' == $name) {
return new Bing($bar);
}
throw new NotFoundException("Invalid foo: $name");
}
}
// ...
class MyServiceProvider implements ServiceProviderInterface
{
public function getFactories()
{
$fooFactory = new FooFactory();
return [
'my-baz' => $fooFactory,
'my-bing' => $fooFactory
];
}
public function getExtensions()
{
return [];
}
}
So, while it's perfectly possible to make a PSR-11 implementation that could work like that, it would not be standards-compliant,[...]
Perhaps I am misunderstanding. There is nothing in PSR-11 that defines a factory signature. Insofar as there is an emerging standard, it is attempting to be codified here in the factories section.
Further, insofar as there is a definition of of the identifier
(service name),
it’s that is is an “opaque string”. Passing that as the 2nd parameter to the
factory and providing a more complete context in the process of delegating
creation seems perfectly acceptable (in fact preferable) to me.
[...]so you will only be able to use your own services (and other services that follow your convention, which there are unlikely to be many).
What I am purposing is that the specified factory signature follow the lead of what is currently implemented in Laminas Service Manager.
If you need auto-wiring capabilities, I suggest you add them on top of standards-compliant implementations and definitions.
It is not auto-wiring that I’m pushing for. It’s the ability to implement Abstract Factories
I apologize for my misleading example. It does obviously illustrate a reliance on class names as service names. I am not purposing any such thing be advocated here.
What I am purposing is that the entry identifier (“any PHP-legal string of at least one character that uniquely identifies an item within a container”) be passed as the 2nd parameter to the factory.
eg:
function (ContainerInterface $container, string $id) : mixed
Thanks for the clarification, @jakejohns! I understand now what you are referring to. Sorry for the initial misunderstanding!
Forgive me if I'm oversimplifying here, but am I right in understanding that what you want in practice is the following?
If so, I would like to suggest a way that already makes that sort of thing possible, but in a different way.
A while ago, one of the subjects discussed here was Delegation. Based on that concept, I created CompositeContainer
, which allows delegating the retrieval of a service to the first matching container from a list of other child containers. If you replace your main container with a composite one, the only child of which is your previously main container, you will get a functionally identical result to what you have right now.
Then, you can write another PSR-11 implementation that you could simply to the above list of children of the Composite containers. This new implementation would naturally receive the service name in its get()
and has()
, and it would have access to the now main Composite container through DI, injecting it into the new implementation. This new child container is then your factory, and you are free to implement instantiation of services from there however you want. You can use an existing container implementation that uses other Service Providers, or write your own custom logic.
The bottom-line is: the thing that receives the service name and has access to the container is the container itself. If you want to add another container with custom logic, including the kind of logic you are talking about, and in a modular way - then you should use a Composite container.
Does this solve your problem?
@jakejohns is there any reason a factory can't handle this as an implementation detail?
class FooFactory
{
public function __construct(private string $name) {}
public function __invoke(ContainerInterface $container) : AbstractFoo
{
$bar = $container->get('my-bar');
if ('my-baz' == $this->name) {
return new Baz($bar);
}
if ('my-bing' == $this->name) {
return new Bing($bar);
}
throw new NotFoundException("Invalid foo: $this->name");
}
}
// ...
class MyServiceProvider implements ServiceProviderInterface
{
public function getFactories()
{
return [
'my-baz' => new FooFactory('my-baz'),
'my-bing' => new FooFactory('my-bing')
];
}
public function getExtensions()
{
return [];
}
}
In other words, is this required for interoperability with this container?
If it's just a feature of this container, it would be simpler for other container implementors if this could be kept as just an implementation detail of this particular container's PSR service provider.
Closing this, as the feature would add unnecessary complexity: if you were able to build a 'foo' => function (...)
key/value pair, obviously you have the service name (the key) and so we don't need to pass this back to you. If your closure needs the key, you can bind the closure to the key.
If I've somehow misread this proposal and the discussion, please feel free to reopen.
Documentation currently says:
However, I sometimes find that passing the name of the service to be useful as well. I would prefer to assume the following signature. Is this wrong?
This allows something like this:
For reference, this is my experimental implementation of a consumer using this assumption:
https://github.com/jnjxp/jnjxp.container/blob/develop/src/Container.php#L66
Is this a problem?