Closed mindplay-dk closed 10 months ago
I've omitted this from the draft PSR, as I have not been able to figure out why this was a listed requirement, the README doesn't explain, and no one seems to recall.
If this needs further discussion, please open a thread in Discussions.
Hey @mindplay-dk ,
I'm reopening this to add some clarification. The 2 pass mechanism is supposed to happen at the service resolve stage.
As you noted, everything that comes out of the two methods is callables - in other words, calling these methods has no side-effects.
However, the order you call factories and extensions matters.
Let's take a sample:
ServiceProviderA
provides a factory for the key Psr\Log\LoggerInterface
(maybe a FileLogger
)ServiceProviderB
extends Psr\Log\LoggerInterface
by wrapping everything in a decorator that preprends the time to the logs.ServiceProviderC
provides another factory for the key Psr\Log\LoggerInterface
(maybe a StdoutLogger
)We have 2 factories for the same key. The spec stipulates that the last Service Provider wins.
Now, the question arise for the extension.
A naive incorrect implementation could:
Psr\Log\LoggerInterface
from ServiceProviderA
ServiceProviderB
(with the decorator that prepends the time)Psr\Log\LoggerInterface
from ServiceProviderC
What I meant to say is that this is wrong.
Instead, we should look at all factories first. A naive, but correct implementation would:
Psr\Log\LoggerInterface
from ServiceProviderA
Psr\Log\LoggerInterface
from ServiceProviderC
ServiceProviderB
In the end, we have a logger that logs to stdout, and that has the time prepended to the logs.
Of course, the implementation above is naive. In a real container, you would never call the factory of ServiceProviderA
and would instead directly call the factory of ServiceProviderC
.
The important part is that extensions must be called AFTER the last used factory and not before. This seems obvious but needs to be said explicitly.
Ah, so in a nutshell, replacing a factory should not wipe out configuration, that's what you meant?
Because the description made it sound like an implementation should cycle through all providers and call getFactories()
, then cycle through all of them a second time and call getExtensions()
.
To my knowledge, most containers don't do that, and don't typically need to?
For the most part, adding a service provider will immediately import and potentially override the container's internal list of factories - then immediately import and append the extensions from the same provider into the container's internal map of lists of configuration functions, if that makes sense?
I will try to think of a better way to explain this, without touching on implementation details.
Thanks for bringing this up, and if you could, please confirm if you think I've understood you correctly now? :-)
I tried to boil it down to the rules + one example, what do you think of this?
1.5. Importing Definitions
When importing service definitions from a provider, containers:
- MUST replace existing factory entries when importing factories from the provider
- MUST preserve existing extensions when replacing factory entries
- MUST append imported extensions from the provider to existing extensions
- MUST NOT remove existing extensions when importing extensions
Example:
- Container has factory A and extension C for service
logger
- Provider defines factory B for
logger
and extension D forlogger
After importing from the provider, the container will contain:
- Factory B for
logger
(because factory A was replaced)- Extensions C and D for
logger
(D was appended after C)
Closing this issue, as nothing further was added to the discussion.
The final draft of the "Importing Definitions" section can be found here.
The proposal currently stipulates that service providers must be called in two passes:
https://github.com/container-interop/service-provider/blob/e04441ca21ef03e10dce70b0af29269281eec6dc/README.md?plain=1#L313-L321
Everything that comes out of the two methods is callables - in other words, calling these methods has no side-effects. I don't see how it could matter in what order the two methods are called?
This change to the README came about in february 2017, under the title Splitting getServices in 2 methods by @moufmouf - does anyone called what this was about? The PR didn't explain it further.
@moufmouf it sounds like you were assuming all of the factories and extension definitions would invoked right away? It would be a pretty strange DI container that bootstraps everything ahead of time? :-)
off-topic, but there are two sections in the README with the same title. :-)
https://github.com/container-interop/service-provider/blob/e04441ca21ef03e10dce70b0af29269281eec6dc/README.md?plain=1#L105-L113