container-interop / service-provider

[EXPERIMENTAL] Promoting container/framework interoperability through standard service providers
72 stars 10 forks source link

2-pass call order requirement #61

Closed mindplay-dk closed 10 months ago

mindplay-dk commented 11 months ago

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.

As a side-effect, even if service provider B is declared before service provider A, it can still extend services declared in service provider A.

@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

mindplay-dk commented 11 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.

moufmouf commented 11 months ago

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:

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:

What I meant to say is that this is wrong.

Instead, we should look at all factories first. A naive, but correct implementation would:

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.

mindplay-dk commented 11 months ago

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? :-)

mindplay-dk commented 11 months ago

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 for logger

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)
mindplay-dk commented 10 months ago

Closing this issue, as nothing further was added to the discussion.

The final draft of the "Importing Definitions" section can be found here.