[PSR-11] Delegate Lookup > Interface Proposal

261 views
Skip to first unread message

Xedin Unknown

unread,
Oct 7, 2016, 12:58:51 PM10/7/16
to PHP Framework Interoperability Group
Hi all,

The delegate lookup feature of Container Interop didn't get as much attention as I feel it deserves. Specifically, it is lacking formalization in PHP - an interface. I started this conversation , but was instructed that the mailing list is the better way to go.
It seems that many would agree with me in that it would be great to have the standard backed by an interface. There was talk of an interface, but the idea was shot down due to forcing the implementation to declare a setter. I completely agree that forcing a setter is a bad idea.
But why not a getter?

XedinUnknown/di is an example of an implementation that would achieve lookup delegation while depending on one method of one interface. The rest is implementation details.
In short, the container that wishes to delegate must pass its parent (or, in my implementation, the "root" parent), if set, to the service definition callable. If the callable is a composite container, it will forward the call to the first child container that contains the definition. Please find a more expanded description in the repo's Readme.

Looking forward to your comments, questions, or suggestions.

David Négrier

unread,
Oct 10, 2016, 6:48:56 AM10/10/16
to PHP Framework Interoperability Group


Hey,

Ok I spent the week-end trying to think about the pros and cons of this proposal.

@XedinUnknown, I'm not sure I understood 100% of your proposal so I'll try to rephrase it. If I'm wrong, let me know.

I understand you are trying to improve the "delegate lookup feature". Your goals are:

- to make it "explicit" that a container is implementing the "delegate lookup feature"
- to make it possible to nest containers at an unlimited level (to be able to nest a composite container in another composite container...)

To do so, you propose to add a new interface named ParentAwareContainerInterface. This interface is exposing a single method "getParentContainer()" that returns the parent container if any (i.e. the composite container containing this container).
When resolving a dependency, the container goes up the chain of parent containers until it reaches the root container. It uses the root container to look up any dependency (just like in the current delegate lookup feature).

Here are a few thoughts about this idea:

First: who is consuming the ParentAwareContainerInterface? This interface is consumed by "child" containers. If a container is a leaf in the container tree (so if it is not a composite container), then there is no need for it to implement the ParentAwareContainerInterface. The interface is trivial to implement and is a good declaration of intent (your container states it supports delegate lookup feature) but it is none the less useless. This interface is only useful for composite containers (so that they can also be bundled into parent composite containers).

Second, each container now needs to find the root container by going up the container tree. In your sample, you do this while resolving a service. This is a bit of a problem performance-wise because this will slow service resolving (and this is a major no no for many implementors). Of course, there is a simple way around that. Finding the root container could be done only once, possibly in the constructor. This assumes that the parent container is passed to the child container constructor (or at build time). This also assumes that the parent container will never change in the life of a container (this is a safe assumption). Looking at @XedingUnknown code, this means that stuff like containers with mutable parents should be forbidden and only containers with immutable parents should be allowed.

If we assume that the "parent" container is always passed to containers in their constructor, it means that containers MUST be constructed in the order of the tree (from top to bottom).



I made a picture so it would be more clear. If the "root container" exists before "Composite A" and if "Composite A" exists before "Container 1", then when "container 1" is created, instead of passing an instance of composite A, it should directly be passed an instance of the root container. That way, "container 1" does not have to go up the tree to find the root container. This makes the proposed "ParentAwareContainerInterface" useless, because finding the root container can be and should be done before the container is created.

@XedingUnknown, I understand your concerns that the "delegate lookup feature" of PSR-11 is not backed by a solid interface. After careful consideration, I'm not sure your proposal of adding a getter really solves any issues (because we already can nest several containers). However, you are making clear that we never spoke about the way containers should be created (and who should come first between the parent and the child container). After thinking about your proposal, what is now clear to me is that the parent should be created before the children (and maybe we should stress that out in the META document).

If I was to add another interface, it would more likely be a "container factory" (a la zend-expressive) that can create a container while passing the parent container in parameter:

interface ContainerFactoryInterface {
    // A composite container could use an array of ContainerFactoryInterface to build containers, passing them the very root container.
    public function createContainer(ContainerInterface $rootContainer);
}


@mnapoli, @mweierophinney, any thoughts about this? Do you think we need to consider this or am I overtinking things?

I think we also need to discuss more here the "delegate lookup feature". It is an optional feature, not backed by an interface and this is unprecedented in other PSRs (correct me if I'm wrong but I don't think other PSRs have similar optional features). To the rest of the members here, do you think we should do something special regarding this? Espceially, how should an implementing container advertise that it is supporting PSR-11 with/without delegate lookup feature?

Best regards,
David.

Xedin Unknown

unread,
Oct 10, 2016, 7:35:07 AM10/10/16
to PHP Framework Interoperability Group
Hi David,

Thank you for taking the time to consider my proposal. While many points expressed by you are true, however, this is now how I see things.

The `ParentAwareContainerInterface` is a formal way of saying "this container can have a parent". Nothing more, nothing less. The "delegate feature" is simply a pattern that is possible as a result of that formalization - because now containers can form a hierarchy, which is impossible (reliably) without this formalized bond. In simpler words, lookup delegation is not a formal albeit optional feature of the standard, but rather something that is possible to implement via the standard. All we're doing is giving implementations the possibility of a reliable hierarchy. If they want to implement lookup delegation, they can now do it.

From this point of view, the consumer of the `ParentAwareContainerInterface` can be any consumer wishing to access the hierarchy, not only a child container. For example, an implementation may want to print or log a string that represents a "lookup path" by giving each container a code. Thus, this interface is useful to any consumer that needs to be hierarchy-aware. Up to now, there is no concept of hierarchy in the standard, and the use of the interface would be to introduce that concept.
Also, using a "definition resolution" mechanism demonstrated in my implementation, one could have as a parent something that is not necessarily a composite container - but maybe simply another container to delegate lookup to. Thus, the concept of "parent" is given an even more precise definition as simply "something that takes precedence in service definition resolution". Kinda similar to how DOM events work.

With regard to the expense of parent lookup. Typical use, as I described in the project's readme, would involve invocation of the `get()` method on the topmost container, which would be the only container that the consuming application needs to be aware of. According to the composite container spec, the composite container does not perform service lookup in the regular sense of this word, but matches the service ID with child containers. Like this, caching (again, typical use) remains on the level of the matching container (the one that matches, i.e. contains the service definition). As a result, parent lookup will only be performed once per service - not because the parent is cached, but because the service referencing the parent is cached. Like this, containers remain independent of their parent, which is a great level of flexibility that may be beneficial in some scenarios. In other words, child containers may be potentially re-attached to other parents, without breaking the hierarchy mechanism. On the other hand, a DI implementation that allows simple values as service definition (e.g. Pimple) can have the correct values retrieved after the parent is switched.
It could also be possible to implement caching on the level of the root container, further increasing speed.

I guess, the point I am trying to make is that everything still remains optional, but is now possible in the scope of the standard specification. I think this is a part of what constitutes a good standard, and therefore I conclude that the `ParentAwareContainerInterface` is still necessary. Also, again in light of the above explanation, I do not believe that "root parent" resolution should necessarily be done before the child container is created. In my proposed approach, it remains an implementation detail that can be done this way.

Looking forward to your further comments, as well as those of other participants!

Xedin Unknown

unread,
Oct 10, 2016, 8:21:55 AM10/10/16
to PHP Framework Interoperability Group
Just wanted to add something.

If I understood correctly, David suggested that all hierarchy is flattened so that all containers have the same parent - the root container - for the sake of faster lookup. If that is true, than I would like to remind everyone that in my proposal, the amount of nesting is implementation detail. If the DI implementation wants to have the same parent for all containers, they can do that. However, I don't see why they must be required to do that.
My demo implementation would work just the same if there was one level, without having to change anything.

Thanks.

I remain,
Anton.

On Monday, October 10, 2016 at 12:48:56 PM UTC+2, David Négrier wrote:

Matthieu Napoli

unread,
Oct 18, 2016, 3:46:00 AM10/18/16
to PHP Framework Interoperability Group
Hi!

This is a lot of text, I'm still not sure I understand the problem we are trying to solve in this thread?

> The `ParentAwareContainerInterface` is a formal way of saying "this container can have a parent". Nothing more, nothing less.

Then we are back to discuss "marker-interface" VS "no-interface", which has already been discussed for a long time: if the interface is useless it shouldn't exist.

> Thus, this interface is useful to any consumer that needs to be hierarchy-aware. Up to now, there is no concept of hierarchy in the standard, and the use of the interface would be to introduce that concept.

Can you point to a real use case that is being used in the wild, and that deserves to be part of a PSR (i.e. that is part of the 80%-90% problem, not just a very isolated use case)?

I understand that a few edge case things might be possible with it, but the very important question is "do we need it?"

Matthieu

Xedin Unknown

unread,
Oct 18, 2016, 5:11:41 AM10/18/16
to PHP Framework Interoperability Group
Hi!

I'm not sure how this could be useless, or an edge case.
Like I wrote, the `ParentAwareContainerInterface` would add the concept of hierarchy to the standard. This creates the possibility for containers to be dependent on other containers for dependency resolution, thus enabling lookup delegation. Lookup delegation does not appear to be an edge case, since it was talked about quite a bit. It could enable all framework components to depend on one single (top-most) container, while at the same time hooking in their own standards-compliant containers - completely transparent to other modules, and even to other parts of the same module. Does this pass as a real use case being used in the wild?
Or are you saying that lookup delegation may not be something that the standard needs? I don't really understand, sorry.

Matthew Weier O'Phinney

unread,
Oct 24, 2016, 5:13:23 PM10/24/16
to php...@googlegroups.com
On Tue, Oct 18, 2016 at 4:11 AM, Xedin Unknown <xedin....@gmail.com> wrote:
> I'm not sure how this could be useless, or an edge case.
> Like I wrote, the `ParentAwareContainerInterface` would add the concept of
> hierarchy to the standard. This creates the possibility for containers to be
> dependent on other containers for dependency resolution, thus enabling
> lookup delegation. Lookup delegation does not appear to be an edge case,
> since it was talked about quite a bit. It could enable all framework
> components to depend on one single (top-most) container, while at the same
> time hooking in their own standards-compliant containers - completely
> transparent to other modules, and even to other parts of the same module.
> Does this pass as a real use case being used in the wild?
> Or are you saying that lookup delegation may not be something that the
> standard needs? I don't really understand, sorry.

Yes, that's essentially what is being suggested here.

As a maintainer on a project that implemented "parent aware"
containers, I can weigh in on this.

zend-servicemanager provides two types of containers:

- The application-level container (Zend\ServiceManager\ServiceManager)
- Context-specific plugin managers

In the v2 series, the plugin managers implemented an interface we
called `ServiceLocatorAwareInterface`. When a factory was invoked by
requesting a plugin, the factory was passed the plugin manager
instance, from which it could pull the parent container in order to
retrieve application-level dependencies. This would be very similar to
your proposed `ParentAwareContainerInterface` usage, I suspect.

What we observed, and what our users reported, was that 99% of the
time, the only reason they used the provided plugin manager *was to
pull the parent container*, as the plugins inevitably would use
application-level services, not other plugins:

function ($plugins) {
$container = $plugins->getServiceLocator();
return new SomePlugin($container->get('SomeDependency');
}

As such, in v3, we removed the concept entirely, simplifying the
majority use case; factories for plugins are always provided the
parent container:

function ($container) {
return new SomePlugin($container->get('SomeDependency');
}

Of course, this means that if you want to retrieve another peer
plugin, you must first retrieve the plugin manager from the container
you receive; as it's the *minority* use case, we felt it was a
reasonable trade-off. (You can provide an alternate "peering"
container if desired, instead of the application-level container, and
this will instead be passed to factories; despite having implemented
that, however, I have yet to see the feature actually used!)

When it comes to *consumer* usage (usage in a factory becomes an
implementation detail; by consumer, I mean the application and/or
diispatcher that pulls services to execute), I have yet to encounter a
use case where you would (a) need to be aware that a container had
parents, and (b) used that functionality. I can only see a use case
*within factories wired to the container*, and, as noted above,
experience and issue reports we've received indicate it makes more
sense to provide the application-level container, as you can then
traverse where needed if you *do* need to pull a context-specific
service.

If this is not what you mean, I respectfully request that you provide
a detailed use case, demonstrating the usage patterns that would
require such an architecture, and demonstrating how the current
interface will not allow for it.

Thanks!

> On Friday, October 7, 2016 at 6:58:51 PM UTC+2, Xedin Unknown wrote:
>>
>> Hi all,
>>
>> The delegate lookup feature of Container Interop didn't get as much
>> attention as I feel it deserves. Specifically, it is lacking formalization
>> in PHP - an interface. I started this conversation , but was instructed that
>> the mailing list is the better way to go.
>> It seems that many would agree with me in that it would be great to have
>> the standard backed by an interface. There was talk of an interface, but the
>> idea was shot down due to forcing the implementation to declare a setter. I
>> completely agree that forcing a setter is a bad idea.
>> But why not a getter?
>>
>> XedinUnknown/di is an example of an implementation that would achieve
>> lookup delegation while depending on one method of one interface. The rest
>> is implementation details.
>> In short, the container that wishes to delegate must pass its parent (or,
>> in my implementation, the "root" parent), if set, to the service definition
>> callable. If the callable is a composite container, it will forward the call
>> to the first child container that contains the definition. Please find a
>> more expanded description in the repo's Readme.
>>
>> Looking forward to your comments, questions, or suggestions.
>
> --
> 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/d6f3e768-668f-4eb0-a6c5-2a94ad190082%40googlegroups.com.
>
> For more options, visit https://groups.google.com/d/optout.



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

Xedin Unknown

unread,
Oct 29, 2016, 6:34:17 AM10/29/16
to PHP Framework Interoperability Group
Hi Matthew!

I am very happy to see that there are already examples of what I meant! And yes, you got it right - the application container rules them all! But this is more of a typical use case, rather than a rule. We are discussing a standard, so I believe that we shouldn't limit usage to specific techniques, but only introduce a helpful concept, codified via an interface in this case.

With regard to usage outside of the service definitions, I'm afraid it's quite hard to come up with an example. However, what I really meant was that no matter why the consumer may need to do that, this approach would allow them to, since it represents the new concept. One example could be of a module that may optimize or otherwise re-arrange the container hierarchy. On the other hand, we can never know what the usage may be, and what patterns may emerge from our standard. We would simply support the "hierarchy" concept via language means.

I think that a standard, while required to take into account specific use cases (and it appears that FIG makes its goal codifying the most common ones) must not limit implementations to those use cases, but instead simply provide a (perhaps language-supported) means of implementing these use cases, while keeping the door open for any other uses. For example, we could name the interface `DelegateLookupContainerInterface`, and it would serve its purpose; but would limit the implementation semantically to only lookup delegation. On the other hand, `ParentAwareContainerInterface` would allow simply a representation of any container hierarchy - maybe for use as a result of parsing an XML document (a DOM of sorts), or to represent a chart, or to delegate service lookup. I don't know what use cases there may be, and am not asserting that my examples are practical. Just expressing my point of view on the concept of "standards".

Bottom line is that I saw the need for an interface in the standard, and came up with an example of what could work, as well as of how it could work. I am now unsure whether the community recognizes this need need, after the comments of @mnapoli What do you think about it?

P.S. Sorry for the delay in response, very busy week.


On Friday, October 7, 2016 at 6:58:51 PM UTC+2, Xedin Unknown wrote:
Reply all
Reply to author
Forward
0 new messages