[PSR-11] Review: should the container ALWAYS return the same instance?

300 views
Skip to first unread message

David Négrier

unread,
Nov 2, 2016, 1:45:49 PM11/2/16
to PHP Framework Interoperability Group
This is a continuation from the discussion about whether PSR-11 containers should always return the same instance.
See https://groups.google.com/forum/#!topic/php-fig/L8rDUwRFsOU for the beginning of the discussion.

<TL;DR;> I believe that we should keep the SHOULD word in place because it does not hurt in practice and because we can require a container to always return the same instance in the yet to come PSR that will define how we put things into a container (see container-interop/service-provider for a proposal). On the other hand, requiring the container to return always the same value would make PSR-11 de-facto incompatible with most containers out there (so should be avoided). However, I think we definitely need to make improvements on the wording that is clumsy (see end of the message). </TL;DR;>

Currently, the spec says:

Two successive calls to get with the same identifier SHOULD return the same value. However, depending on the implementor design and/or user configuration, different values might be returned, so user SHOULD NOT rely on getting the same value on 2 successive calls.  

Hari, Larry, Pedro are asking if we could strengthen the SHOULD to a MUST (as in: Two successive calls to get with the same identifier MUST return the same value).

For instance, Larry says:

> I would prefer to have get() defined to always return the same object (lazy loaded or otherwise, not relevant here), as that is the typical case, and another method added (either on the same interface or a separate one, I am flexible) to handle the factory use case.  That way I know the behavior of each object I get back, specifically in relation to whether I have a private copy or not.

First of all, let me say if I was starting from a blank slate, I would definitely agree with you and put a MUST here. But we are not starting from a blank slate and many containers out there offer the possibility to create new services each time you call "get". For instance Pimple (with the factory method), or Symfony (with the "shared" option: http://symfony.com/doc/current/service_container/shared.html)
I definitely want Pimple and Symfony in PSR-11 (or at least I want to be able to create an adapter for those) and putting a "MUST" in the requirements makes this impossible. So if we decide to force containers to always return the same value, we'd better have a very good reason to do so.

Now, what is the real impact of this?

You all seem to imply that if we don't put a "MUST" here, then we cannot "trust" the container to give us a usable instance. The container might be "vicious" and decide to fool its user by giving him a new instance while the user was expecting the same instance. But this is not how it works. The typical use case for ContainerInterface is to allow a consumer (like a router) to be "container agnostic". As a user, I can decide to plug any container to a given router. The router will then be able to fetch the correct controller (or the correct action if you do ADR) based on the controller name. In this scenario, the user is the one in charge of configuring the container (and putting the controller in the container). If the user decides that he wants to use a container that can act as a factory and that he wants to configure some services that are created on each request, it is his responsibility. Keep in mind that since we do not standardize how to put things in the container, it has to be the user responsibility to configure his container.

So saying that a container MUST return always the same instance is premature. When we will start to standardize how to put things in a container, then we can define that configured instances MUST be the same. But we can do this in the next PSR (the one that defines how to put things into a container) and not in this one.


Now, even if I'm really convinced that we should keep the "SHOULD" in place, I understand from the numerous comments that the way the spec is written is clumsy (or rather worrisome).

Why did I propose this sentence in the first place? I was thinking that a "CompositeContainer" could try to cache in an array the list of services already fetched from its children. This is something you can do if you know the children containers will always return the same instance but that you cannot do if you don't know if the container will return the same or a new instance. So really, the "SHOULD" prevents us to write composite containers that "cache" the result from children. This is actually not a very big deal, as composite containers are very seldomly used. What I was trying to say here was: "Hey, don't cache the services returned by a container, it might return a new instance on the next call". I proposed this wording 2 years ago in container-interop. In hindsight, this is certainly too specific to be part of a PSR.

I see 3 possible solutions to replace this:

Solution 1:
Let's completely drop the 2 sentences => PSR-11 does not address whether a container should return the same instance or not (this is something we can deal with when we define how to put things into a container)

Solution 2:
Let's only keep the first sentence: Two successive calls to get with the same identifier SHOULD return the same value.

Solution 3:
Let's rewrite the second sentence: Two successive calls to get with the same identifier SHOULD return the same value. A container MAY offer the possibility to return a new value for some identifiers if the user configured it accordingly.
(The wording of solution 3 should be improved but you get the idea)

@larry, @hari, @pedro: do you favor one of those solutions or do you still think we should use a "MUST" in the spec?
@matthieu, @mathew: any preference regarding the solutions proposed above?

We should also try to ping more container authors to get their advice on this issue. I'd be interested in knowing what Fabien Potencier or Taylor Otwell think about this.

++
David

Matthieu Napoli

unread,
Nov 2, 2016, 5:46:06 PM11/2/16
to PHP Framework Interoperability Group
To me the existing wording is fine, solution 3 sounds good too to make it clearer.

First of all I don't see how we could have a MUST: many (most?) containers would be immediately incompatible.

Quoting Larry:

That way I know the behavior of each object I get back, specifically in relation to whether I have a private copy or not.

Could you explain a case where it would matter? I think it would be better if we discussed of a concrete problem.

Hari K T

unread,
Nov 3, 2016, 12:55:37 AM11/3/16
to php...@googlegroups.com
Hi David, 

Thanks for taking your time looking into this.

I am for Solution 2.

I am also interested to hear from Fabien considering the sentence 

"However, depending on the implementor design and/or user configuration, different values might be returned, so user SHOULD NOT rely on getting the same value on 2 successive calls."

is primarily written to satisfy symfony, pimple ( and any other containers I am not aware of ) . I am also not sure if PSR-11 is passed, will symfony, pimple( or any major players ) will  integrate in the upcoming release or do they only push these on the next major versions ?

I don't know if Paul M Jones is around here to talk about Aura.Di, but he expressed about the same over  https://github.com/container-interop/container-interop/issues/69#issuecomment-251435392

I would suggest pushing PSR for the future than for short living ones. So even if anyone don't implement it now, but they could use it on their next major version.

Thank you

Pedro Cordeiro

unread,
Nov 3, 2016, 5:58:51 AM11/3/16
to PHP Framework Interoperability Group
> The typical use case for ContainerInterface is to allow a consumer (like a router) to be "container agnostic". 

I completely agree. I can't think of a use case where a router would depend on having either shared instances or exclusive instances of a controller, so I'll change this example a little. Hopefully this answers @Matthieu's question too.

Let's say I have a framework component (and ORM, maybe) that triggers events (to log queries, maybe). This part of my framework fetches an event dispatcher (let's say, symfony's) from the registry, and it HAS to be a shared entry (otherwise, I'll just dispatch an event on a new instance that has no listeners).

Now, my consumer, the ORM, is completely container agnostic. It only works, however, with SOME specific implementations (that ALWAYS return shared instances), and fails on SOME others (that ALLOWS for factories). 
I don't know (and can't control) how the entries were set, because I deferred this wiring to the application realm, by design. I don't know (and can't control) which container this is. If I can't guarantee it will work with ANY PSR-11 implementation, I cannot depend on the contract (PSR-11) - I'll have to depend of an implementation, which is bad.

The only instances where I could depend on a 100% generic PSR-11 container is when I don't care at all if the service being retrieved is exclusive or shared. And there only needs to be ONE place where I need a specific kind of instance, and my entire framework will depend on a specific implementation. The event dispatcher service is one example where I need a shared instance -- the ORM could be another. I don't want new connections being opened, I don't want two separate units of work with two different in-memory caches. If I fetch my ORM twice from the container, I expect to be given the same instance, not two different separate instances. If I fetch it twice from separate consumers, I still expect to be given the same instance.

Now, I'll reiterate that I understand strengthening the SHOULD to a MUST would greatly hinder this PSR's adoption. I just want to know if you guys understand my point and if you guys still disagree that this is an issue, and why. I don't know if I'm failing to see it, but as far as I can think about it, not knowing what kind of instance the container will yield would also greatly restrict the possible uses and hinder its adoption -- and I'm not even talking about things out of scope here, because I'm failing to see its usefulness even for consumer-only frameworks, like shown on the previous examples.

David Négrier

unread,
Nov 4, 2016, 11:45:29 AM11/4/16
to PHP Framework Interoperability Group
Hi Pedro,

I understand your example (about the event dispatcher that MUST be shared), but still, I disagree this is an issue with PSR-11.

You say:


Now, my consumer, the ORM, is completely container agnostic. It only works, however, with SOME specific implementations (that ALWAYS return shared instances), and fails on SOME others (that ALLOWS for factories). I don't know (and can't control) how the entries were set, because I deferred this wiring to the application realm, by design. I don't know (and can't control) which container this is. If I can't guarantee it will work with ANY PSR-11 implementation, I cannot depend on the contract (PSR-11) - I'll have to depend of an implementation, which is bad.

When you say: It only works, however, with SOME specific implementations (that ALWAYS return shared instances), and fails on SOME others (that ALLOWS for factories), I disagree. If we use "solution 3" I'm proposing above, you should really write: It only works, however, with SOME specific configurations (that ALWAYS return shared instances), and fails on SOME others (for containers that CAN provide non-shared services and that have been configured accordingly)

Now, you say: I don't know (and can't control) how the entries were set, because I deferred this wiring to the application realm, by design.

When you say "I", I suppose you mean "the ORM author", am I right? Of course, you are right to say the ORM author can't control if the event dispatcher is shared or not. And it is not its job. And I'm sure you agree with me if I say that the ORM is not using ContainerInterface directly (it is the container how creates the ORM's EntityManager and injects the event dispatcher in it.

So in the end, the wiring of the app is deferred to the application realm (i.e. the end user). If the end user badly screws up its configuration and decides to configure a non-shared event dispatcher, that's its problem (it's a bug), not a problem with PSR-11. Of course, with a more restrictive PSR-11 (that would force shared services only), this kind of bug would not be possible. But we loose compatibility with all the containers out there and it's just not worth it in my opinion.

Finally, we will inevitably end up working on a PSR that defines how we put things in a container. In this PSR, we will need to be very strict regarding the fact that we set a shared or a non-shared service.
If you haven't done so yet, you should definitely take a look at container-interop/service-provider. This project proposes a way to put things into a container (and I hope will be the basis of a future PSR). I just submitted a PR (here: https://github.com/container-interop/service-provider/pull/33/files) that makes sure that containers consuming a service provider MUST provide shared services. So even if PSR-11 allows for non-shared service, container-interop/service-provider explicitly forbids it for services provided in service providers.

TL;DR; I understand your sample about the event dispatcher being a non-shared service. This would definitely be an issue. I just disagree that it is a concern with PSR-11. It is a concern with either the user configuring the container, or the next PSR to come that defines how we put things into a container.

Does it make more sense?

++
David.

Théo FIDRY

unread,
Nov 4, 2016, 12:13:39 PM11/4/16
to PHP Framework Interoperability Group
It may be naive of me, but the must is a no go: I personally never need it but I believe that if most DIC out there have a `share` option to have control on that behaviour, there is a reason and valid use cases.

Returning or not the same instance should depend on how you are registering your service, not how you retrieve it. As such, I think "should" is the appropriate word and if you have a case where you must always have the same instance, or a new one each time, then you should have integration tests for it in your application anyway.

In practice, the only case when you have this problem is when you are writing a library. But even in this case, provided this behaviour is critical, you should have integration tests with some implementations.

Pedro Cordeiro

unread,
Nov 4, 2016, 12:19:03 PM11/4/16
to PHP Framework Interoperability Group
Hi, David. 

Thank you again for your response.

I understand your point of view. You don't think there is anything the ORM should do to prevent fetching a non-shared instance, because that would be the responsibility of the application. I kind of agree, to a limited scope.

But if you allow me to press on this issue just a little longer... it still doesn't change the fact that I can still have two different containers: ContainerShared, that only configures/fetches shared services, and ContainerFactory, that only fetches new instances. They would both be PSR-11 compliant, and still, incompatible for most cases. And then, it wouldn't be a configuration issue (a bug) on the application end, it would be a component incompatibility - which kind of defeats the point of having a interoperability standard. It'd still be a little odd saying something like "this ORM can be used with PSR-11 compatible containers, except for containers that don't have the option to configure shared services (like ContainerFactory)".

Choosing a standardized component is not a configuration bug, right? Should we let two PSR-11 implementations be incompatible for many (if not most) cases, in the name of backwards compatibility (with containers that still don't extend PSR-11)?

You tell me this concern is out of scope. I don't disagree. I'm just saying that, IMO, the current scope is not enough to assure two different implementations are actually compatible - and thus, interoperable, which is kind of the point here. And there is something we could do to fix this, without broadening the scope -- limit what kind of services (shared or exclusive) "get" can return. This leads us to a different issue, which is compatibility with current implementations.

To be completely honest here, with best practices in mind, I can't even think of why a framework component (the ORM, in this case) should even be container-aware. It should have its dependencies declared directly in the constructor (or in separate setters) and just trust someone else (the application) will inject them. A container should be just one of the framework's components, not part of its core/kernel.

Pedro Cordeiro

unread,
Nov 4, 2016, 12:26:59 PM11/4/16
to PHP Framework Interoperability Group
And now that I think about it, solution 3 works best for me. It'd forbid having something like I described on the previous email (ContainerFactory), because there'd need to be an option to fetch shared instances.

Larry Garfield

unread,
Nov 4, 2016, 8:55:38 PM11/4/16
to php...@googlegroups.com
Pedro did a fine job of explaining the sort of use case where I'd expect to see potential issues with a lack of clarity about whether a service is shared or not.  Frankly I don't find the "out of scope" argument to be compelling; I as a consumer care, therefore it is in scope to me as a consumer.

The only compelling argument I've seen so far is the BC issue for existing container implementations, which is a valid consideration.

To that end, I'd prefer to get input from Fabien and Taylor, as David suggests, before weighing in on the listed options.

--Larry Garfield
--

Hari K T

unread,
Nov 5, 2016, 1:06:48 AM11/5/16
to php...@googlegroups.com
This was a discussion by Taylor regarding the same : https://github.com/container-interop/container-interop/issues/6 .

Hari K T

You can ring me : +91 9388 75 8821

Skype  : kthari85
Twitter : harikt

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/8711ead1-3aa7-d9e4-caad-04852cf2338f%40garfieldtech.com.

For more options, visit https://groups.google.com/d/optout.

Matthew Weier O'Phinney

unread,
Nov 7, 2016, 6:34:34 PM11/7/16
to php...@googlegroups.com
On Fri, Nov 4, 2016 at 7:55 PM, Larry Garfield <la...@garfieldtech.com> wrote:
> Pedro did a fine job of explaining the sort of use case where I'd expect to
> see potential issues with a lack of clarity about whether a service is
> shared or not. Frankly I don't find the "out of scope" argument to be
> compelling; I as a consumer care, therefore it is in scope to me as a
> consumer.
>
> The only compelling argument I've seen so far is the BC issue for existing
> container implementations, which is a valid consideration.
>
> To that end, I'd prefer to get input from Fabien and Taylor, as David
> suggests, before weighing in on the listed options.

I'll weigh in, as we're a project that provides a concept of shared vs
unshared services.

zend-servicemanager shares services by default. That's the expected
use case 99% of the time. However, there are occasionally services you
may not want to share, and for that we have the ability to mark
services as *not* shared. One example already raised in this thread is
event manager instances; these are specific to each service composing
them, but are themselves injected with a shared event manager instance
for sharing listeners across event manager instances.

Another situation is with some of our plugins: validators, filters,
etc. that may be stateful, and require separate instances as many
instances with different configuration may be in play.

From a consumer perspective, this is something *you* are in control
of. It is something specific to *your* application, where *you* have
made the decision of what container you are using, and thus have
control over the *configuration* of it.

From a practical standpoint, consider Expressive. We provide
functionality for setting up three different container implementations
currently: Aura.Di, Pimple, and zend-servicemanager. We provide some
functionality for bridging configuration between them in the file that
creates the container instance. But final application configuration
and container configuration *is up to you, the developer*.

So far, it's not posed any problems. If you do not want an instance
shared, you configure it that way.

From the *API* perspective, it doesn't matter, because the code that
consumes the container is only exercising the container-interop API,
and assumes that *you*, the developer, have configured the container
the way you need to.

As such, I totally agree with the statement that whether or not the
instance returned by get() is shared or not is out of scope; that's in
scope for a PSR that details *configuring* a container, as the point
at which you decide whether or not an instance is shared is during
configuration, not *consumption*. PSR-11 is about *consuming* services
from a container.
> --
> 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+u...@googlegroups.com.
> To post to this group, send email to php...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/php-fig/8711ead1-3aa7-d9e4-caad-04852cf2338f%40garfieldtech.com.
>
> For more options, visit https://groups.google.com/d/optout.



--
Matthew Weier O'Phinney
mweiero...@gmail.com
https://mwop.net/

David Négrier

unread,
Nov 8, 2016, 5:38:14 AM11/8/16
to PHP Framework Interoperability Group
Hi Matthew,

Thanks a lot for your answer.

You say:


> As such, I totally agree with the statement that whether or not the
> instance returned by get() is shared or not is out of scope; ...

If you had to choose one option in the 3 solutions I propose at the top of this thread, does it mean you would favor solution 1? (i.e. let's completely remove the sentence stating that a container SHOULD return the same entry)
Or would you still mention expected default behaviour (as in solution 2 or 3)?

Thanks a lot,
David.

Pedro Cordeiro

unread,
Nov 8, 2016, 7:55:38 AM11/8/16
to php...@googlegroups.com
Hi, Matthew!

I understand everything you said. About the configuration responsibility, this PSR's scope and the fact that it's coherent to have non-shared services in some contexts. I never disagreed with any of that.

I'll try to be more objective here.

Given two containers: 

a) "ContainerShared", that implements PSR-11 and ONLY returns shared instances, no option to configure non-shared services;
b) "ContainerFactory", that implements PSR-11 and ONLY returns non-shared instances, acts as a factory always.

Do you agree that:

1) These implementations are perfectly fine PSR-11 implementations, as it's phrased right now?
2) These implementations are incompatible between themselves for pretty much every use case, including zend-expressive, that is currently the most successful case of container interoperability?
3) The incompatibility between these two containers is not an application/configuration issue, but plain lack of interoperability between them?

If you agree with (1), (2) and (3), the final question that stands is: is it still OK to have container implementations that are NOT interoperable implementing an interoperability standard?

I understand zend-servicemanager is a coherent implementation, that allows for both shared and non-shared services, and that configuring which services are shared is up to the application. I don't disagree.
I understand that most current container implementations allow for both kinds of services, shared and non-shared, and that it'd be up to the application to configure which is which. I don't disagree.

But certain implementations (as presented above) could still be 100% incompatible for its consumers - not because *I*, the application developer, configured something wrong (a bug), but because the *CONSUMING* standard (PSR-11) simply allows for non-interoperable container implementations.

If *YOU*, zend-expressive developer, were to develop a truly agnostic container consumption implementation based on PSR-11, you'd have to document something to the effect of: "while zend-expressive is compatible with PSR-11 containers in general, it's not meant to be used with factory-only containers. Choose accordingly.". THAT is what strikes me as something weird to have.

I'd choose (3) over (1), but (1) is better than (2). Enforce shared-only services or allow shared/exclusive services to coexist, explicitly disallowing factory-only implementations. If you can't do either, having factory-only containers should be highly discouraged on the META document and the standard shouldn't even mention the possibility of having non-shared services (even though it'd allow by omission).

If you disallow factory-only implementations, you can truly blame the application configuration for any misuse of the container. But if you allow factory-only implementations to coexist with shared-only implementations, you end up with a non-interoperable interoperability standard.


> To post to this group, send email to php...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/php-fig/8711ead1-3aa7-d9e4-caad-04852cf2338f%40garfieldtech.com.
>
> For more options, visit https://groups.google.com/d/optout.



--
Matthew Weier O'Phinney
mweiero...@gmail.com
https://mwop.net/
--
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+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Pedro Cordeiro
Produto
 

Logo assinatura

O site com o maior número de eventos à venda do Brasil!
www.sympla.com.br

Matthieu Napoli

unread,
Nov 8, 2016, 8:32:34 AM11/8/16
to php...@googlegroups.com
Hi Pedro,

1) These implementations are perfectly fine PSR-11 implementations, as it's phrased right now?
Yes

2) These implementations are incompatible between themselves for pretty much every use case …

Why? Having a concrete use case would help here I think.

3) The incompatibility between these two containers is not an application/configuration issue, but plain lack of interoperability between them?

Is that a question? ;)

Matthieu

Hari K T

unread,
Nov 8, 2016, 10:00:52 AM11/8/16
to php...@googlegroups.com
Hi, 

Probably the only container that don't have a configuration option to return shared / non shared instance will be Aura.Di I guess.

If you are not familiar with Aura.Di : 

If you are calling get method, it will always give the same instance ( provided you set the service ) .
If you are calling newInstance method, it will always return new instances.

I was basically thinking of this Interface before.

interface ContainerInterface 
{
    public function get($id);
    public function has($id);
    public function make($id, $params = [], $setters = []); // may be different name 
}

But many of you think standardizing factory is a different PSR.

In that case why not have something like 

interface ContainerInterface 
{    
    public function get($id, $params = [], $setters = [], $shared = true);
    public function has($id);    
}

You may say this probably is a BC break for container or , but you could get rid of this BC break via something like this .


The $params and $setters are for in case the container need to alter the values passed on run time.

Thank you

Hari K T

You can ring me : +91 9388 75 8821

Skype  : kthari85
Twitter : harikt

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Alexandru Pătrănescu

unread,
Nov 8, 2016, 10:02:50 AM11/8/16
to php...@googlegroups.com
Hi Pedro,

You articulated your problem well. Thanks.

If you have a need to use a container so that it must return a shared or new instance than using the PSR-11 interface directly is not a solution.
Just as everyone said, PSR-11 is not addressing this problem.

I said this another time in another thread:

Having in mind the fact that you can't rely on obtaining a shared/new instance from the container, can you still use the PSR-11 interface?
My answer is yes, considering some practiced that people might say are good practices anyway:

Use the container to inject stateless services only.
Use as much as possible only stateless services so that you will need only one instance of it in a php process.
You should separate state: value objects should be different class than service objects. Value object keep state, service objects execute logic using state as state as input/output.

If you really need to have a service with state, don't inject it directly but inject the stateless service object that can give you the instance you need. That is a factory in case you always need to create a new instance or is a registry in case you need a multiton pattern or a singletons wrapper in case you really need to be sure you are using the same instance like for an event manager.

So you see, you can build some tools to wrap a PSR-11 interface. You will not use it directly for all cases but probably there won't be so many cases where you have non-stateless services if you try to do it.
This is how I use the container-interop interface and it's working.
Anyway, you should build your classes agnostics of PSR-11. Only the "application" classes will use it to wire things up.

Hope I was helpful with my point of view.

Regards,
Alex


Larry Garfield

unread,
Nov 8, 2016, 10:56:35 AM11/8/16
to php...@googlegroups.com
Alexandru,

I agree 99% with your comments regarding what should be considered good practice when dealing with DI or a DIC.  That's what one should be doing.

However, that does not in any way say that people *will* do that, and as noted most existing DIC implementations have the option to behave otherwise.  You say the correct approach is to inject a factory service and get a one-off object out of that. I (mostly) agree.  But as written, the spec explicitly says that it could be used as a factory, but I as a consumer cannot tell if what I'm getting is a factory or not.

A factory-only container (legal in PSR-11 right now) would be mostly incompatible with a database connection, or any other object that is backed by an IO socket directly or indirectly.  Fun times. :-)

As Pedro well-explains, that means in any non-ideal circumstances (ideal: everything in the container is completely stateless both directly and indirectly) the behavior of the service I get from the container may vary depending on whether it's shared or not, and I as a consumer can't tell the difference until it results in subtle effects on my application that will be hard to track down.

--Larry Garfield

David Négrier

unread,
Nov 8, 2016, 11:35:50 AM11/8/16
to PHP Framework Interoperability Group
Hi Pedro,

I think I'm starting to understand why we are having this discussion.

I'll go back to your first post in this thread. At some point, you say:


> If I can't guarantee it will work with ANY PSR-11 implementation, I cannot depend on the contract (PSR-11)

If I'm right, this is the "root" issue for you.
Interestingly enough, this is not a concern for me. Not at all.

Being able to replace ANY PSR-11 implementation by ANY other is not the goal of PSR-11.

Try to change the way you think about the usefulness of an interface and think about it this way: it gives you the power to choose what implementation is best suited to you.

If you use a router that is compatible with PSR-11, then you are free to choose ANY PSR-11 container to plug it into the router. Now, you can choose what container you prefer based on the features it proposes. If you like non-shared services and want to use them, choose a container that supports them. If you need support for lazy services, choose a container that has this feature, etc... Of course, if your application uses non-shared services and if you want to switch from one container to another, you will have to choose a container that also supports non-shared services. But really, that's ok. I'm perfectly fine with letting containers embrace and extend PSR-11.

I'll even go further. Have a look at this "container":

class ConfigurationContainer implements ContainerInterface
{
    public function get($id)
    {
        $value = getenv($id);
        if ($value === false) {
            throw new NotFoundException("Entry ".$id." not found.");
        }
        return $value;
    }
   
    public function has($id)
    {
        return getenv($id) !== false;
    }
}

See what it does? It is a simple "container" that exposes environment variables.
Is it a valid PSR-11 container? According to the spec, it is.

If you disagree that it is a valid container, then we should rework the spec to specify that a container MUST be able to store objects, arrays, values, etc... (and as Matthew noted, this is best suited for the PSR that will describe how we store objects in a container).

Furthermore, the sample container above could prove to be very valuable.
Even if it is useless by its own, I could perfectly compose it with another container (using the delegate lookup feature that still needs to be addressed). The idea came to me after reading this article and after working a bit with Docker that relies heavily on environment variables.

It's exactly the same for the theoretical "factory-only" container Larry is speaking about. I would certainly not rely on it on its own, but if I had such a container, I could very will compose it with a container that has no support for non-shared services (like Mouf or Aura.DI).

Those containers are valid. Yes, they are "exotic", but we need to allow exotic containers. IMO, they can be useful, especially when composed with other containers.

David.

> To post to this group, send email to php...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/php-fig/8711ead1-3aa7-d9e4-caad-04852cf2338f%40garfieldtech.com.
>
> For more options, visit https://groups.google.com/d/optout.



--
Matthew Weier O'Phinney
mweiero...@gmail.com
https://mwop.net/

--
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+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.
--
Pedro Cordeiro
Produto
 

Logo assinatura

O site com o maior número de eventos à venda do Brasil!
www.sympla.com.br

--
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+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Pedro Cordeiro

unread,
Nov 8, 2016, 11:57:45 AM11/8/16
to PHP Framework Interoperability Group
Hi, David.

I think we have found the root of our disagreement, yes!


> Being able to replace ANY PSR-11 implementation by ANY other is not the goal of PSR-11.

What is the goal of this standard, if not interoperability? While different implementations can (obviously) have different sets of features, they should at least agree on a minimum that makes them interoperable. If "get" allows vastly different behaviors, that CAN make containers non-interoperable for certain consumers (some of which have been addressed by myself and Larry), then what would be the point of having this standard?

> Now, you can choose what container you prefer based on the features it proposes. If you like non-shared services and want to use them, choose a container that supports them. If you need support for lazy services, choose a container that has this feature, etc...

I might have specific needs for specific features, which by definition wouldn't allow me to rely on any kind of abstraction. In this case, I'd code to an implementation, not to a contract. It makes no sense even typehinting a PSR-11 container on my component's constructor if my component can't work with any PSR-11 implementation. I'd typehint a specific implementation instead.

And I'm not even talking about standards only. Let's take a minute to remember Liskov's Substitution Principle. If my component accepts an object of type S, it should also accept any subtype of S. If my component accepts a ContainerInterface, it should accept any ContainerInterface implementation. This is OOP, not standards goals.

Alexandru and Matthieu, I think Larry already addressed my concerns here too.

Matthew Weier O'Phinney

unread,
Nov 8, 2016, 2:41:15 PM11/8/16
to php...@googlegroups.com
On Tue, Nov 8, 2016 at 4:38 AM, David Négrier <david....@gmail.com> wrote:
> Hi Matthew,
>
> Thanks a lot for your answer.
>
> You say:
>
>> As such, I totally agree with the statement that whether or not the
>> instance returned by get() is shared or not is out of scope; ...
>
> If you had to choose one option in the 3 solutions I propose at the top of
> this thread, does it mean you would favor solution 1? (i.e. let's completely
> remove the sentence stating that a container SHOULD return the same entry)
> Or would you still mention expected default behaviour (as in solution 2 or
> 3)?

Honestly, given that the "SHOULD" verbiage makes several people
uncomfortable, I'd opt for solution 1, dropping the sentences. We can
then potentially cover it in the meta document detailing the "why"
behind omitting any detail around the return value.
> https://groups.google.com/d/msgid/php-fig/9a71d63b-55e5-4b2b-98d1-fb704ed05396%40googlegroups.com.

Matthew Weier O'Phinney

unread,
Nov 8, 2016, 2:54:08 PM11/8/16
to php...@googlegroups.com
On Tue, Nov 8, 2016 at 6:55 AM, Pedro Cordeiro <pedro.c...@sympla.com.br> wrote:
Hi, Matthew!

I understand everything you said. About the configuration responsibility, this PSR's scope and the fact that it's coherent to have non-shared services in some contexts. I never disagreed with any of that.

I'll try to be more objective here.

Given two containers: 

a) "ContainerShared", that implements PSR-11 and ONLY returns shared instances, no option to configure non-shared services;
b) "ContainerFactory", that implements PSR-11 and ONLY returns non-shared instances, acts as a factory always.

Do you agree that:

1) These implementations are perfectly fine PSR-11 implementations, as it's phrased right now?
2) These implementations are incompatible between themselves for pretty much every use case, including zend-expressive, that is currently the most successful case of container interoperability?
3) The incompatibility between these two containers is not an application/configuration issue, but plain lack of interoperability between them?

If you agree with (1), (2) and (3), the final question that stands is: is it still OK to have container implementations that are NOT interoperable implementing an interoperability standard?

I don't agree with your assessment at all.

I agree with point 1. I don't agree with either point 2 or point 3.

In the majority of use cases I've encountered, the workflow goes something like this:

- Application composes a container.
- Application does some sort of request routing to determine what service to dispatch
- Application pulls that service from the container and dispatches it

This means that, regarding point 2, whether or not instances are shared is typically moot. They are pulled from the container generally once, and once only, in any given request. (Yes, there are times when multiple retrieval may happen, particularly in large, event-based applications where multiple listeners may use the same services.)

However, this leads to point 3: it's absolutely an application/configuration issue. You need to choose the container that suits your application needs and configure it accordingly. The PSR-11 specification is only around *retrieving* services. It is specifically not detailing how those services are stored within, whether or not the service MUST return the same instance, etc. The point is that if you have a container, can you (a) test if the service is present, and then (b) retrieve it?

The *behavior* aspect — if the service is shared or not, and how to configure services in the container — is purposely intended for a future PSR that covers that aspect.

For a consumer, they can typehint on ContainerInterface and exercise its API. They retrieve the service, and do something with it. Whether or not that service is shared or the correct instance expected will be entirely up to the container implementation they choose, and how they configure services in that container. So I disagree with your third point as well.
 
<snip>

If *YOU*, zend-expressive developer, were to develop a truly agnostic container consumption implementation based on PSR-11, you'd have to document something to the effect of: "while zend-expressive is compatible with PSR-11 containers in general, it's not meant to be used with factory-only containers. Choose accordingly.". THAT is what strikes me as something weird to have.

Honestly, Expressive *does not care*. At the application skeleton level, we're not working with shared instances; we're working with a router, an error handler, and whatever middleware we pull from the container. The question of *shared* services comes up for the *application developer*, which is why Expressive *provides a choice of containers*: to allow the application developer to choose the container implementation that suits their project. As long as that container is compatible with the PSR, it's compatible with Expressive, plain and simple, because all we do is exercise its API.

Pedro Cordeiro

unread,
Nov 9, 2016, 7:23:33 AM11/9/16
to PHP Framework Interoperability Group
Hi, Matthew! Thanks for replying :)

Larry and I presented a few cases where the framework relies on its components having a shared instance, instead of exclusive ones. Every framework component that fetches services that handles connections, IO sockets, in-memory caches, and others HAVE to be given shared instances. Being fed new (empty) in-memory caches every time is crazy, right? :) If you could address how we can deal with these cases with factory-only containers, maybe it could be clearer to me.

I don't really agree that most services are only fetched once by the framework -- an event dispatcher (that traditionally has an in-memory registry of listeners that must be shared) could be fetched various times by various different components - to register new listeners or to simply dispatch events. It would depend on the framework here. Maybe a microframework tends to fetch less, but a fullstack framework could really differ in that aspect.

Like I said before, I would agree that it's an application/configuration issue if all containers were interoperable on a behavior level. If the user had misconfigured a container to act as factory for a specific service, where it should be retrieving shared instances, ok, that's a bug, not this PSR's fault. However, once we decide to allow factory-only containers, we define that there is no way to configure these containers properly for components that need shared instances (such as the ones I mentioned previously). Hence, we have a compatibility issue.

Again, choosing the right containers for my use is not a configuration/application issue. PSR-11's goal SHOULD be container interoperability. Which means containers should agree on a minimum behavior to be interoperable. If we need to code to specific implementations, than a standard is not needed at all.

I won't dwell any more into this issue, because I feel like I'm bashing the same key over and over again. I feel like I've said all I had to say in that aspect, and I only have the best intentions in mind for this PSR. If you all feel like it's not a big issue, then let's move on. I do feel like it's a big issue, though.

David Négrier

unread,
Nov 10, 2016, 6:33:27 AM11/10/16
to PHP Framework Interoperability Group
Hi Pedro,

First of all, I thank you again for your comments. Do not believe we do not consider them. Actually, your argument about the Liskov substitution principle got me thinking the last 24 hours (hence my lack of quick reply :) ) Your comments are always welcome as they force us to think harder about what we are doing and in the end, they lead to a better standard.

So... what about the Liskov Substitution Principle. Are we violating it?

From Wikipedia: Liskov's notion of a behavioral subtype defines a notion of substitutability for objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program

Liskov Substitution Principle does not state that "S" that is a subtype of "T" can be replaced with "S' " that is also a subtype of "T".

Also, we are subtyping an interface (not a class). To violate the LSP, the implementation must be "semantically different" from what is described in the documentation of the interface. So actually, the less we put in the documentation and the less we are likely to violate the LSP. In that regard, I'm leaning (like Matthew) towards solution 1 (let's simply remove the whole 2 sentences and let's not specify at all that 2 successive calls to "get" SHOULD return the same value. We simply don't care).

As such, I'm willingfully lowering the expectation a user can have on the interface (the interface does not say much about what a container is). Therefore, I'm putting the "burden" of checking that the container he is choosing actually does what he expects to do (and is properly configured) But this is alright.

The more I think about it, the more I believe this is the right thing to do.

For the sake of the example, let's make a contraposition and let's try a "reductio ad absurdum".

If we want ANY implementation to be replaceable by ANY other implementation, we obviously need to define that 2 successive calls to "get" MUST return the same value.
But that's not enough. Currently, the spec states that "A call to get can return anything (a mixed value)"

We would need to rewrite that as: "Containers implementing this spec MUST be able to return anything (any possible mixed value returnable by PHP)".

At this point, we are actually dictating that the container implementations MUST support a way to define entries via a callback (because it is really the only way you can return any possible mixed value). For instance, it would be very hard for a container based on configuration alone to be able to return a resource, or a generator (which are valid return values).

Another solution could be to restrict the allowed return types (for instance: a container MUST only return objects, or a container MUST only return objects and scalars). If we write this, then Pimple and all containers supporting the creation of services via a factory (i.e. most containers out there) need to be rewritten to actually prevent the return of other values.

At this point, maybe you start to realize that having strong expectations on the behaviour of a container is actually "dictating" how it should work internally.

If you need yet another argument, let's have a look at PSR-6 and the values that are allowed to be passed: http://www.php-fig.org/psr/psr-6/#data

PSR-6 states that: Implementing libraries MUST support all serializable PHP data types
PSR-6 does not state that: Implementing libraries MUST NOT support PHP data types that are not serializable

Now, let's have a look at Stash's Ephemeral driver (https://github.com/tedious/Stash/blob/master/src/Stash/Driver/Ephemeral.php)
This driver is essentially storing data in an array. This means that I can store a database connection or a GD image resource in this array (these are not serializable). It is absurd but it will work. Of course, if I do this, I cannot expect to switch from the Ephemeral driver to a MemcacheDriver later. Is it a problem with the Ephemeral driver? I don't think so. Does the Ephemeral driver violates the LSP? Nope. The Ephemeral driver is simply extending what is allowed by the PSR-6 and the user is having wrong expectations that these extended features are available in all implementations.


To sum it up, we are having this debate because counter-intuitively, PSR-11 does not define what a container is. By not defining it, it leaves the user *in charge* of choosing a container that answers its needs. We could of course go the other way and define precisely what a container is, but containers are so different out there that we would be excluding most of them by doing so. If we go that way, we need a good reason to do it. But so far, I don't see a real use case good reason. Our experience with container-interop has shown us that being very "liberal" about the definition of a container is not a problem. We can still have practical use cases (like Zend-Expressive or mnapoli's Invoker). So I'm keeping my opinion (and leaning towards Solution 1)

++
David.

Pedro Cordeiro

unread,
Nov 10, 2016, 8:52:14 AM11/10/16
to php...@googlegroups.com
Hi, David. :)


First of all, I thank you again for your comments. Do not believe we do not consider them. Actually, your argument about the Liskov substitution principle got me thinking the last 24 hours (hence my lack of quick reply :) ) Your comments are always welcome as they force us to think harder about what we are doing and in the end, they lead to a better standard.

I know you all take these discussions into consideration, what I meant was simply that I had already spoke my heart and that I probably wouldn't have anything else to add =).

Since you've replied to my concerns about LSP, let me address it too.

Liskov Substitution Principle does not state that "S" that is a subtype of "T" can be replaced with "S' " that is also a subtype of "T".

It kind of does, though a simple syllogism.

1) ContainerInterface is not an implementation, but is a type nonetheless.
2) Any implementation of ContainerInterface is a subtype of ContainerInterface.
3) LSP dictates I should be able to use any (variable S) subtype of ContainerInterface wherever a ContainerInterface is acceptable, without altering any of the desirable properties of that program.
4) Therefore, subtypes of ContainerInterface should not differ amongst themselves in how they affect the desirables properties of my program.
Conclusion: If my program accepts S, it's under no obligation of accepting S'. But if it accepts T, it should accept both S and S'.

Overall, I agree that solution (1) is better than the current wording. I'm not sure if I agree with the other things that we have being disagreeing about (mainly, if this is a in-scope or out-of-scope issue), and I have nothing new to add in that aspect.

Even though I have a feeling I'm outnumbered in my opinion, this has been a very interesting conversation. Thank you kindly for taking the time to respond each single time :)

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Daniel Hunsaker

unread,
Nov 10, 2016, 3:35:13 PM11/10/16
to PHP Framework Interoperability Group
Certainly feels as though the separation of container-get from container-set has caused a lot of disagreement, here.  It also looks like there's a *lot* of feedback here saying there need to be at least two interfaces in container-set - SharedContainer and  FactoryContainer - and that many projects will need (or at least probably choose) to wait until they can rely on one or both of those to actually use a container PSR.

In the meantime, other projects will likely be satisfied with PSR-11 more or less as-is, if for no other reason than to claim support for it.  I imagine almost all of these will be frameworks and similar libraries, or container libraries themselves.  Other projects, especially end-user projects, will see far less utility in a get-only PSR, and will therefore delay adoption until the set portion is ready as well.  Which, near as I can see, is fine for the aims of this particular PSR, which seems to be designed more for frameworks than for application developers.

As to the specific question asked here, either option 1 or 3 would fit best, with 3 being more explicit in the expected behavior of a compliant implementation, especially as regards the need for further verification of how the container is configured (be that via implementation or interface).  I'd probably mention the -set interfaces in there somewhere as a point of reference (the PSR can be amended with the actual number(s) when such is/are assigned), but that's me; it may not be desirable in this context.

Gary Hockin

unread,
Nov 11, 2016, 5:53:10 AM11/11/16
to PHP Framework Interoperability Group
It's important to reiterate that the implementation of whether services  share or not is exactly that - an implementation detail. For me, this is not something that the spec should concern itself. I would love to see the existing container-interop standard considered more here too. The current standard has two methods - `get` and `has` - the rest are implementation details. While I understand that this is not enough for some, for me, this allows perfect interoperability, and this is why we are copying it as a "formal" PSR. As an example, I wanted to drop Zend\ServiceManager as the container into Slim, and because of the container-interop interface, and because they were compatible, it was a simple job.

Interoperability standards are not designed to make all implementations interchangeable in all cases. It's entirely possible that someone writes a PSR-11 compatible container that returns pre-configured services that get their config from a database that is specific to their needs, obviously this would not be able to be dropped into any other project anywhere. The idea is to allow the real people in userland to be able to replace a container implementation with another one WHERE COMPATIBLE. My above example using Slim is a perfect example of where this standard works. 

I understand that this is a lot of repetition of other people's points, but for me, it is worth repeating. Don't forget; I could write a PSR-3 compatible logger that had dependencies on all sorts of stuff within my project that would mean it can't be dropped into any other project. At a later date, I could replace that with any other PSR-3 logger, however, because that's how interop should work.

G
Reply all
Reply to author
Forward
0 new messages