[REVIEW] PSR-11 Container Interface

588 views
Skip to first unread message

Matthew Weier O'Phinney

unread,
Oct 26, 2016, 9:28:30 AM10/26/16
to php...@googlegroups.com
Hello, everyone!

PSR-11, Container Interface (née container-interop) has been in incubation for a
couple years now, and the editor and sponsors feel it is ready for review. As
coordinator, I hereby open the mandatory review period prior to a formal
acceptance vote; voting will begin no earlier than 13:30 UTC on Wednesday,
November 9th, 2016.

For the current version, please reference:

- https://github.com/php-fig/fig-standards/blob/a47c644f9d0f914bb0a9777eeaec157f2d51dbff/proposed/container.md
- https://github.com/php-fig/fig-standards/blob/a47c644f9d0f914bb0a9777eeaec157f2d51dbff/proposed/container-meta.md

The related library containing the interfaces is here:

- https://github.com/php-fig/container

Additionally, a large number of projects either implement or consume
container-interop already, and would be (relatively) immediately compatible:

- https://github.com/container-interop/container-interop#compatible-projects

Please familiarize yourself with the specification and its scope, and reply on
this thread with any concerns.

Thanks!

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

Stephan Hochdörfer

unread,
Oct 26, 2016, 9:58:39 AM10/26/16
to PHP Framework Interoperability Group
Just wondering which PHP version are we targeting with the PSR? Or to be more precise: Shouldn't we add parameter type hints and return type declarations where possible?

Stephan Hochdörfer

Hari K T

unread,
Oct 26, 2016, 9:59:19 AM10/26/16
to php...@googlegroups.com
Hi,

The only concern to me is https://github.com/container-interop/container-interop/issues/69 regarding return value of get . 

Basically this is the text : 

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.

I don't want to drag everyone again to the same discussion. But I wonder if there is any implementors having any concern about the same.

Thank you

--
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/CAJp_myX6QenJQtgmkDYRmJp6ffEaCfO3gDDKHOur1Sq4biLWRw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Andreas Heigl

unread,
Oct 26, 2016, 10:02:45 AM10/26/16
to php...@googlegroups.com
Hi all.

Am 26.10.16 um 15:58 schrieb Stephan Hochdörfer:
> Just wondering which PHP version are we targeting with the PSR? Or to be
> more precise: Shouldn't we add parameter type hints and return type
> declarations where possible?

As PHP5.6 is supposed to be supported until Dec. 2018
(http://php.net/supported-versions.php) I'd opt against PHP7 only.

That would - at least in my eyes - hinder adoption.

And I'm sure the PSR will be published before Dec. 2018.

Just my 0.02 €

Cheers

Andreas
--
,,,
(o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:and...@heigl.org N 50°22'59.5" E 08°23'58" |
| http://andreas.heigl.org http://hei.gl/wiFKy7 |
+---------------------------------------------------------------------+
| http://hei.gl/root-ca |
+---------------------------------------------------------------------+

Matthew Weier O'Phinney

unread,
Oct 26, 2016, 11:25:24 AM10/26/16
to php...@googlegroups.com
As Andreas already pointed out, it makes sense to support the lowest
current supported stable PHP version, which is currently 5.6. As such,
parameter and return type declarations cannot be used yet.

Matthew Weier O'Phinney

unread,
Oct 26, 2016, 11:34:08 AM10/26/16
to php...@googlegroups.com
On Wed, Oct 26, 2016 at 8:59 AM, Hari K T <ktha...@gmail.com> wrote:
> The only concern to me is
> https://github.com/container-interop/container-interop/issues/69 regarding
> return value of get .
>
> Basically this is the text :
>
> 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.
>
> I don't want to drag everyone again to the same discussion. But I wonder if
> there is any implementors having any concern about the same.

No issues from my POV as maintainer on zend-servicemanager.

The reason is thus: we have always supported a concept of "shared"
services. Services are shared by default, but when configuring the
container, you can mark a service as "not shared"; this can be useful
when a particular service *should* have multiple instances. As an
example, our EventManager is not designed to be shared between
different services, but has a constructor pattern that *does* require
access to other services, and thus management by the container.

Similarly, Pimple 1 separated shared from unshared services, and v3
allows you to make services unshared by mapping a service to the
return of the container's factory() method.

While it could be considered niche functionality, having the ability
to act in this way covers a more generalized set of functionality,
which, more importantly, is already seen in existing implementations,
making it useful to the specification.

Hari K T

unread,
Oct 26, 2016, 10:58:02 PM10/26/16
to php...@googlegroups.com
Hi @mwop,

Thank you for your comments.

I am writing inline for I don't miss anything.

> The only concern to me is
> https://github.com/container-interop/container-interop/issues/69 regarding
> return value of get .
>
> Basically this is the text :
>
> 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.
>
> I don't want to drag everyone again to the same discussion. But I wonder if
> there is any implementors having any concern about the same.

No issues from my POV as maintainer on zend-servicemanager.

The reason is thus: we have always supported a concept of "shared"
services. Services are shared by default, but when configuring the
container, you can mark a service as "not shared"; this can be useful
when a particular service *should* have multiple instances.

1. According to this PSR itself configuring a service is out of scope. So different implementors have different ways. And some of them don't have any way itself. ( Eg : Aura.Di ) . It has newInstance method ( https://github.com/auraphp/Aura.Di/blob/76824bdeae99e46e6ae06f29e4bdf86063da86b9/src/Container.php#L531 ) to get new instance each time.

2. I love this issue https://github.com/container-interop/container-interop/issues/44 for the same . According to https://gist.github.com/mnapoli/6159681 there are some implementations that rely on make() we probably needs something else though. I noticed you suggested zf3 uses build. We can look into naming later.

You can argue that you can pass an extra parameter to get ( I am looking at https://github.com/container-interop/container-interop/issues/6#issuecomment-30773191 ) or create wrappers like  https://github.com/container-interop/container-interop/issues/69#issuecomment-251050021 . But the problem is in-consistency with get method on different containers.

As an
example, our EventManager is not designed to be shared between
different services, but has a constructor pattern that *does* require
access to other services, and thus management by the container.

Sorry that I am not familiar with Zend EventManager for the current time. I will try to find some code or docs what you were pointing to.
 
Similarly, Pimple 1 separated shared from unshared services, and v3
allows you to make services unshared by mapping a service to the
return of the container's factory() method.

Yes you are right, that is where we were arguing https://github.com/container-interop/container-interop/issues/69
 
While it could be considered niche functionality, having the ability
to act in this way covers a more generalized set of functionality,
which, more importantly, is already seen in existing implementations,
making it useful to the specification.

I would say as this PSR don't care how the configuration of DI is done, it will be good to have one more method according to me which can return new instances.

I don't care about the name that is going to be used, but a consistency would always be nice.

Thank you.

David Négrier

unread,
Oct 27, 2016, 7:48:01 AM10/27/16
to PHP Framework Interoperability Group
Hey Hari!

You say:

> Basically this is the text :
>
> 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.
>
> I don't want to drag everyone again to the same discussion. But I wonder if
> there is any implementors having any concern about the same.

Just like @mwop, I've never had an issue with this behaviour.
Actually, I'd be interested in knowing what is your concern with this? Do you have an actual use case in mind that absolutely requires the container to always return the same value for all entries?

We can of course work on standardizing the notion of "factories" like described here (https://github.com/container-interop/container-interop/issues/44) but this is IMHO a whole different PSR as factories may/should not be containers.

Thanks a lot,
David.

Pedro Cordeiro

unread,
Oct 27, 2016, 9:04:24 AM10/27/16
to PHP Framework Interoperability Group

> Just like @mwop, I've never had an issue with this behaviour.
> Actually, I'd be interested in knowing what is your concern with this? Do you have an actual use case in mind that absolutely requires the container to always return the same value for all entries?

A simple example is an event dispatcher. If I register a listener on one instance of the dispatcher and trigger an event on another instance of the dispatcher, my registered listeners will not be called. Again, this is an application concern, not a framework concern and it could be argued that this kind of issue is out-of-scope for this PSR.

But overall, I agree with Hari that the main use case for a container is to instance the service once and always return the same instance on further calls. If you retrieve new instances from your container on each call, your container is acting like a simple factory, not as a service container/dependency injection container.

Pedro Cordeiro

unread,
Oct 27, 2016, 9:09:06 AM10/27/16
to php...@googlegroups.com
A container, like the name says, should "contain" services, not create services on each call, return them and then discard them.

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

David Négrier

unread,
Oct 27, 2016, 10:32:25 AM10/27/16
to PHP Framework Interoperability Group
Hi Pedro,

Interestingly enough, I think we completly agree.

If you look at your sentence, you say: "A container should "contain" services, not create services on each call, return them and then discard them."
Notice that you used the word "should", not "must".

This is exactly what the PSR is stating: "Two successive calls to get with the same identifier SHOULD return the same value."

Because this is what we expect overall from a container.

Yet, some containers give you the freedom to have a new entry returned at each call. In this case, the container acts as a factory. Should we completely forbid that?
I see no point in putting a hard ban on it.

As you noted in your post, if the user configured its container so that a new dispatcher is injected in each service, this is configuration issue, not an issue with the standard. The user is simply misusing its container.

Also, many containers already allow creating new entries at each call to get. For instance, in Pimple, you can do this with the `factory` method.

If the PSR stated "Two successive calls to get with the same identifier MUST return the same value.", Pimple (and many other containers) could never be compatible with PSR-11.
This would be a shame as it would hinder adoption, without so far any clear benefits.

So I'd really want to keep this "SHOULD" in place.
However, maybe the second sentence can be worked upon.

Maybe the problematic part is: "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."

Of course, the user knows how it configured its container so it can rely on it to return the same entry on SOME known identifiers. If you have an idea on how to rephrase this in a more intelligible way, I'd be interested.

Best regards,
---
David.
Twitter: @david_negrier
Github: @moufmouf


Le jeudi 27 octobre 2016 15:09:06 UTC+2, Pedro Cordeiro a écrit :
A container, like the name says, should "contain" services, not create services on each call, return them and then discard them.
2016-10-27 11:04 GMT-02:00 Pedro Cordeiro <pedro.c...@sympla.com.br>:

> Just like @mwop, I've never had an issue with this behaviour.
> Actually, I'd be interested in knowing what is your concern with this? Do you have an actual use case in mind that absolutely requires the container to always return the same value for all entries?

A simple example is an event dispatcher. If I register a listener on one instance of the dispatcher and trigger an event on another instance of the dispatcher, my registered listeners will not be called. Again, this is an application concern, not a framework concern and it could be argued that this kind of issue is out-of-scope for this PSR.

But overall, I agree with Hari that the main use case for a container is to instance the service once and always return the same instance on further calls. If you retrieve new instances from your container on each call, your container is acting like a simple factory, not as a service container/dependency injection 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.

Pedro Cordeiro

unread,
Oct 27, 2016, 12:56:43 PM10/27/16
to php...@googlegroups.com
As you noted in your post, if the user configured its container so that a new dispatcher is injected in each service, this is configuration issue, not an issue with the standard. The user is simply misusing its container.

I understand where you are coming from, and it makes sense from a framework's developer point of view. This is the same conversation we had a while ago, about the scope of this PSR. 

However, as an application developer, this PSR doesn't help me much as is, because:

a) I still need adapters for each specific implementation to add services to the container;
b) even though adding services to the container is out of scope for this PSR, I still need to know how to add a service in order to figure out what a specific implementation of `get` will yield (a new instance or a shared instance).

So, as an application developer, the fact that this PSR doesn't enforce either way makes even retrieving services (which is the scope of this PSR) unusable without having to adapt my application to specific implementations.

Now, to reiterate, I understand this was proposed under FIG 2.0 and that its goal is to help interoperability between the frameworks themselves, not to allow for agnostic implementations on enduser's applications. I'm just pointing out that if setting a service is unknown AND getting a service doesn't enforce shared objects/new instances, I can't truly know in my application how it's going to behave. 

My main complaint is: I can end up with a class that depends on a PSR-11 container (a contract/abstraction) but is incompatible with some implementations of PSR11, and it struck me as something weird to have. This basically excludes the possibility of depending on PSR-11 in my applications.

A possible new PSR that defines how to set/add services shouldn't extend PSR-11 because of this. It'd need to redefine if `get` should return a shared/new instance DEPENDING on how the service was set.

Currently, I can have a 100% PSR-11 compliant container that either:
a) returns new instances each time `get` is hit;
b) instantiates on the first hit, returns a shared instance always;
c) returns either a new instance each time or a shared instance each time, depending on the "set"/"add" implementation (which is out of scope here).

Pedro Cordeiro

unread,
Oct 27, 2016, 1:04:06 PM10/27/16
to php...@googlegroups.com
To phrase it better: specifying that setting a service is out of scope for this abstraction and yet having this abstraction's behavior depend on how the service was set sounds really inconsistent and greatly limits the possible use cases for this standard.

David Négrier

unread,
Oct 27, 2016, 4:29:53 PM10/27/16
to PHP Framework Interoperability Group
Hi Pedro,

> However, as an application developer, this PSR doesn't help me much as is...

Absolutely true. You are also right to note that this PSR is not directly targeted at end users but rather at frameworks.

You should view container interoperability as a multistage rocket.

The first stage of the rocket is PSR-11. Out of the box, even if you can't put things into the container, PSR-11 is still useful. Have a look at Zend-Expressive if you want a great example. Zend-expressive is allowing you to use many routers and many containers. The router is instantiating the controller through ContainerInterface `get` method. This way, the router is not dependant on one specific container (consider how this better than in Silex for instance, where Pimple is the only possible container). This is already a great win.

The second stage of the rocket is to be able to put things in the container. A best candidate we have found so far to fulfill this task is universal service providers. If you want a taste of what it might look like, head over to https://github.com/container-interop/service-provider/ (this is alpha so be gentle and forgiving :) ). You will notice that PSR-11 is a prerequisite for those service providers.

You say:


> A possible new PSR that defines how to set/add services shouldn't extend PSR-11 because of this.

I beg to disagree. In container-interop/service-provider, containers consuming the ServiceProvider interface MUST return a shared instance for services contained in the service provider.

Yes, a container can in some cases return new services for some entries. But I'm still capable of writing a service-provider PSR that dictates that consuming containers MUST return shared services for services contained in my service provider. Both statements are not incompatible.

++
David
Twitter: @david_negrier
Github: @moufmouf

Hari K T

unread,
Oct 28, 2016, 1:34:59 AM10/28/16
to php...@googlegroups.com
Hey David, 

Thank you for your comments. I will try to write what I am looking for.
 
> Basically this is the text :
>
> 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.
>
> I don't want to drag everyone again to the same discussion. But I wonder if
> there is any implementors having any concern about the same.

Just like @mwop, I've never had an issue with this behaviour.

Yes, I looked into the zend event manager https://docs.zendframework.com/zend-eventmanager/quick-start/ to understand what @mwop was mentioning. It do contain a shared event manager instance , so if someone need to pass that shared instance probably I don't know what they really do.
 
Actually, I'd be interested in knowing what is your concern with this? Do you have an actual use case in mind that absolutely requires the container to always return the same value for all entries?

I may be wrong, but these are the few things I have in mind. You can say it is application level configuration, but at the end of the day every PSR is used by application developers.

1. Getting an instance of SignalManager : Aura makes use of single shared signal manager, but you can make it as not shared also.


2. Database connections are always shared one. You never expect different connections each time.

You can argue to create wrappers or do in the configuration level. But for a specification it should be very clear what it returns as Pedro Cordeiro mentioned in this thread.

Always returns new instance 

or 

You will always get the same object.
 
We can of course work on standardizing the notion of "factories" like described here (https://github.com/container-interop/container-interop/issues/44) but this is IMHO a whole different PSR as factories may/should not be containers.

I don't think it need to be a different PSR itself for every containers have the functionality for the same. Either in configuration or having a different method. So why not suggest a method itself ?

I understand that the object creation inside Factory can be using "new" keyword than using the container itself ie why you suggested a different PSR. You can create a different PSR for sure. 

So in that case as in discussion https://github.com/container-interop/container-interop/issues/69#issuecomment-251316682 it will be something like 

class SomeContainer implements ContainerInterface, FactoryInterface  {}

Now your container's get can return new or shared instance depending on configuration and make / create can create new instances.

Didn't we removed the configuration level of container to return shared / new instances if we say "get" always return same instance and "make / create" always create new instance ?

As this discussion started 2 days before and none of the existing FIG 3.0 members have the problem may be this is only concern for me. We can wrap this up if there is no further issues raised.

Thank you

Larry Garfield

unread,
Oct 28, 2016, 7:04:04 PM10/28/16
to php...@googlegroups.com
On 10/28/2016 12:34 AM, Hari K T wrote:
You can argue to create wrappers or do in the configuration level. But for a specification it should be very clear what it returns as Pedro Cordeiro mentioned in this thread.

Always returns new instance 

or 

You will always get the same object.
 
We can of course work on standardizing the notion of "factories" like described here (https://github.com/container-interop/container-interop/issues/44) but this is IMHO a whole different PSR as factories may/should not be containers.

I don't think it need to be a different PSR itself for every containers have the functionality for the same. Either in configuration or having a different method. So why not suggest a method itself ?

I understand that the object creation inside Factory can be using "new" keyword than using the container itself ie why you suggested a different PSR. You can create a different PSR for sure. 

So in that case as in discussion https://github.com/container-interop/container-interop/issues/69#issuecomment-251316682 it will be something like 

class SomeContainer implements ContainerInterface, FactoryInterface  {}

Now your container's get can return new or shared instance depending on configuration and make / create can create new instances.

Didn't we removed the configuration level of container to return shared / new instances if we say "get" always return same instance and "make / create" always create new instance ?

As this discussion started 2 days before and none of the existing FIG 3.0 members have the problem may be this is only concern for me. We can wrap this up if there is no further issues raised.

I fully agree that the same-instance and new-instance-each-time use cases exist and are valid.  However, I am also not entirely comfortable with conflating the two.

If I, as a caller (say, in a factory or a dispatcher or one of the few places where talking to a container is legit) call get(), do I have that object uniquely or do I share its instance with others?  With the spec as written, I do not know.

In an ideal world, all service objects would be immutable post-construction so the question would be largely irrelevant.  That is not, however, the case, and many service objects have setters on them (often for very legitimate reasons, such as a service that is a proxy to many other services).  If I call one of those addSubThingie() methods on that service, am I modifying a global instance or not?  That's very relevant to my usage of that object.  However, I cannot know in the current spec if it's private or not.

Additionally, non-service objects sometimes end up in the container, too.  No, that's not a great idea but it still happens from time to time.  Are those value objects global or not?  Again, very relevant to my usage of them but not at all clear unless I know how the implementation I'm using is built, at which point it's no longer interoperable.

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.

That is, yes, I would like to see the SHOULD strengthened to a MUST.

--Larry Garfield

Larry Garfield

unread,
Oct 28, 2016, 8:03:52 PM10/28/16
to php...@googlegroups.com
Other thoughts, in a separate reply as they're not directly related to
the other discussion, not really in any special order:

* The spec should specify what legal characters are for an entry
identifier, and what if any reserved characters there are. It should
also specify minimum supported key length and character sets, for
completeness. I recommend borrowing PSR-6's language here, which I
believe addresses this area well. Whether it uses the same reserved
character list or another one I don't feel strongly about. I would not
consider this a Review-breaking change.

* The allowance for additional parameters on get() is worrisome. That
implies ANY use of a second or further parameter sets me up for an
incompatibility, but implementations are free to add those
implementation-specific hooks that I can couple to. I would much prefer
to omit that allowance entirely. (Technically optional parameters
cannot be prevented, but we shouldn't encourage it.)

* What other exceptions might get($id) throw besides NotFoundException?

* I would much prefer to see a DependencyNotFoundException defined for
that case than leaving that up to individual implementers. Standard
error handling is just as important if not moreso than the happy path.

* The text of the spec says that the psr/container package doesn't exist
yet, but Matthew's post above links to it. :-)

* Section 4 of the metadoc: I like! Thank you.

* I don't believe any other spec put exceptions into their own
sub-namespace. That seems unnecessary to me, and I'd prefer to remove
that sub-namespace and just leave the exceptions in Psr/Container.
There's no reason to have this inconsistency.

* In metadoc 7.3.2, there's this line:

throw NotFoundException::entryNotFound($identifier);

Which seems to come out of nowhere. The interface doesn't define a
static method factory. The hypothetical MissingDependencyException
later in the method just uses the constructor. The example here should
do the same and use the constructor.

Now for the big one:

Metadoc 8.2 seems redundant with section 1.4 of the spec, as in, nearly
identical. It doesn't add anything so should just be removed. Assume
someone reading the metadoc has already read the spec and has questions
about it.

The section on delegate containers strikes me as very odd. It describes
behavior that is... inherently possible with any interface of almost any
kind. Composing other objects of the same interface is one of the core
features of interfaces, so describing it here is almost entirely
redundant, especially when no actual mechanism for such composition is
defined. I understand that such composition is a key feature of the
spec, but the spec doesn't actually define how to do it other than "ya
know, it's possible because interfaces". That seems needlessly redundant.

What it does seem to define is that delegates should be looked up
exclusively for indirect dependencies. Which... does not make any sense
to me at all.

Reading both section 1.4 of the spec and 8 of the metadoc, I am still
confused about how I would "properly" implement the delegation concept
at all. The example (with the pictures) assumes the composite container
is empty of its own services, and is just delegating in order to other
containers. The spec, however, says that get() should operate on self,
but dependencies on child containers. That implies the composite
container has its own services. The metadoc discussion (8.7) talks as
though that's temporary until the rest of the market catches on and gets
around to just providing empty composite containers... but then the spec
still doesn't tell me what I'm supposed to do in this case.

Moreover, the spec and metadoc are inconsistent. The spec says that a
container must

- provide some way to specify child (delegate) containers (which could
vary by implementation)
- call a direct-get() on self only
- call a dependency-get() on child only
- call has() on self only
- But maybe will dependency-get() on self, if they really mean it

This last point is highly troublesome as it makes behavior unpredictable.

However, the metadoc in section 8.3 says the expected usage is:

- A composite container (new term) forwards all get() calls to its child
containers until one is found
- A non-composite container forwards all dependency-get() calls to
its... parent composite container
- Loop until done

That's not at all the same logic. As a potential implementer, I have no
idea what I'm supposed to do if my composite container has its own
services. Do I forward them or not? Also, can any container be a
composite container or no? Can a composite container be nested inside
another? If so, to where do I forward get() calls?

The spec refers only to a delegate container, but I'm using the terms
parent and child. That's because when you have a reference from one
container to another, that's a parent-child relationship (object with
the reference to the object being referenced). What it appears needs to
happen for what the metadoc describes (which is not what the spec
describes) is for the composite container to have a reference to all of
its sub-containers, and then the sub-containers to also have a reference
back to it in order to delegate their dependency-get() calls. That
creates a circular reference. While those are not the memory leak they
once were in PHP, circular references should almost always be seen as a
code smell. That I do not know how to implement this pattern without
them is a sign that something is very wrong.

Moreover, it seems to suggest that I cannot nest containers
indefinitely. If Container2 has multiple delegate containers defined,
then... what happens? It must, or else CompositeContainer is
fundamentally different than a Container, but it's not defined in the spec.

Now, consider the case where container2 delegates to composite container
but the service it's looking for doesn't exist anywhere.
CompositeContainer would have to call has() on each sub-container in
turn, and then call get() if found. But has() specifically doesn't
delegate, so they can't have nested containers. So, nested containers
aren't really banned, just not actually possible to implement, but
that's not stated.

Moreover, all of this logic seems to then exist in get() or sub-calls
therein. That makes get() considerably more expensive with multiple
internal calls and branch points. From Drupal I know that the
container's lookup process is very much along the critical path of the
application; adding even one method call to a get() lookup results in,
for our case, hundreds of additional stack calls (not cheap in PHP) over
the course of a request. That's a performance hit we would be unwilling
to swallow.

At this point I'm completely lost, and write this portion of the spec
off as unimplementable. Clearly there are some implementations that are
doing something with it, as noted in the metadoc. However, from the
spec and metadoc itself, which is all I am voting on (I did not look at
the linked discussion threads either, as those are not what I'm voting
on and the purpose of the metadoc is to summarize those in one place), I
don't know how I'd even implement this correctly, or in a manner that
would let me assemble different containers that didn't know about each
other without intimately knowing about all of them, and even then it
would be creating an object graph with multiple circular references,
which is a code smell.

Quite simply, as written I cannot vote for this spec with the delegate
logic as ill-defined as it is, and the only way I can see to even try to
implement it resulting in such a slow and loopy object and call graph.

I will leave these comments here for response before suggesting any
alternatives.

--Larry Garfield

Pedro Cordeiro

unread,
Oct 31, 2016, 7:57:05 AM10/31/16
to PHP Framework Interoperability Group
Hi, David. Thank you for your response.

Let me address some points.

> Out of the box, even if you can't put things into the container, PSR-11 is still useful.

I still have a lot of issues trying to think of use cases where I don't need to know if the objects I'm retrieving are shared instances or not. Since I can't know if the objects are shared, I have to assume they're not. Doesn't matter if I'm an application or a framework developer. I can't think of how a framework could reliably implement something that consumes a service from a PSR-11 container without having this information. I agree with Larry 100% here.

$obj = $container->get("something");
$obj->setThing("myValue");
$obj2 = $container->get("something");
var_dump($obj2->getThing());

This snippet here has unknown behavior, not because I don't know the properties of the service (which is completely out of scope), but because I don't know the behavior of the "get" method (which is completely in-scope).

I understand we don't want to hinder adoption of PSR-11 by being too restrictive (and thus locking some implementations out) and I don't have a solution to this problem. However, I still believe this is something to be addressed. 

While writing a framework (as a set of reusable components), I wouldn't invoke any containers directly, I'd just rely on having something elsewhere injecting my components' dependencies. While writing my application (or a bootstrapper/kernel for the framework, which acts as a bridge between the framework and the application) is when the container is most useful - and I 100% need to know how it behaves there! As an example, Symfony provides a kernel can be thought as part of the application domain, not the framework domain. It's the base of the "skeleton application" that wires everything together. The other components have no knowledge of a container. The application is doing all the wiring/retrieving of services - and thus needs to know how the wiring behaves (or, at the very minimum, how fetching a service from a container behaves).

> * The allowance for additional parameters on get() is worrisome. That 
> implies ANY use of a second or further parameter sets me up for an 
> incompatibility, but implementations are free to add those 
> implementation-specific hooks that I can couple to.  I would much prefer 
> to omit that allowance entirely.  (Technically optional parameters 
> cannot be prevented, but we shouldn't encourage it.) 

Two different implementations of PSR-11 should be compatible between themselves and switching between them shouldn't break any implementations that depend on a generic PSR-11 container. If anything, I'd specify that there MUST NOT be any additional parameters in that call. I'm 100% with Larry on this one too.

I don't really have a strong opinion on the other issues Larry raised, even though I agree with most of them.

David Négrier

unread,
Nov 2, 2016, 10:19:41 AM11/2/16
to PHP Framework Interoperability Group
Hi Larry, Hi Pedro,

Wooh! That's a lot of comments. Thanks to both of you.
There are really many different issues that need to be addressed in those review (especially in Larry's posts). I'll not respond directly in this thread (otherwise it will be hard to follow each issue individually.
Instead, in the coming days, I'll open new threads to address each individual issue raised (starting with the MUST vs SHOULD debate :) )

This way, we can keep the PSR-11 review readable and understandable.

Thanks,
David.

Chuck Burgess

unread,
Nov 5, 2016, 9:33:07 AM11/5/16
to php...@googlegroups.com

Small point: should the has() section in 1.1 say that the entry identifier MUST be a string, similar to how it does so for get()?


--
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.

Matthieu Napoli

unread,
Nov 5, 2016, 12:47:01 PM11/5/16
to PHP Framework Interoperability Group, Demon...@gmail.com
Hi Chuck, good point I've opened a PR to fix that: https://github.com/php-fig/fig-standards/pull/832

For those not aware I just wanted to point out that topics in this thread are being discussed in separate threads on the mailing list. That helps to keep up with each discussion. If a topic was overlooked please open a thread on the mailing list.

Matthieu
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages