1 Delegate lookup feature Meta Document
2 =====================================
7 This document describes the *delegate lookup feature*.
8 Containers are not required to implement this feature to respect the `ContainerInterface`.
9 However, containers implementing this feature will offer a greater lever of interoperability
10 with other containers, allowing multiple containers to share entries in the same application.
11 Implementation of this feature is therefore recommanded.
16 The [`ContainerInterface`](../src/Interop/Container/ContainerInterface.php) ([meta doc](ContainerInterface.md))
17 standardizes how frameworks and libraries make use of a container to obtain objects and parameters.
19 By standardizing such a behavior, frameworks and libraries relying on the `ContainerInterface`
20 could work with any compatible container.
21 That would allow end users to choose their own container based on their own preferences.
23 The `ContainerInterface` is also enough if we want to have several containers side-by-side in the same
24 application. For instance, this is what the [CompositeContainer](https://github.com/jeremeamia/acclimate-container/blob/master/src/CompositeContainer.php)
25 class of [Acclimate](https://github.com/jeremeamia/acclimate-container) is designed for:
27 ![Side by side containers](images/side_by_side_containers.png)
29 However, an instance in container 1 cannot reference an instance in container 2.
31 It would be better if an instance of container 1 could reference an instance in container 2,
32 and the opposite should be true.
34 ![Interoperating containers](images/interoperating_containers.png)
36 In the sample above, entry 1 in container 1 is referencing entry 3 in container 2.
43 The goal of the *delegate lookup* feature is to allow several containers to share entries.
48 ### 4.1 Chosen Approach
50 Containers implementing this feature can perform dependency lookups in other containers.
52 A container implementing this feature:
54 - must implement the `ContainerInterface`
55 - must provide a way to register a *delegate container* (using a constructor parameter, or a setter, or any
56 possible way). The *delegate container* must implement the `ContainerInterface`.
58 When a *delegate container* is configured on a container:
60 - Calls to the `get` method should only return an entry if the entry is part of the container.
61 If the entry is not part of the container, an exception should be thrown (as required in the `ContainerInterface`).
62 - Calls to the `has` method should only return *true* if the entry is part of the container.
63 If the entry is not part of the container, *false* should be returned.
64 - Finally, the important part: if the entry we are fetching has dependencies,
65 **instead** of perfoming the dependency lookup in the container, the lookup is performed on the *delegate container*.
67 Important! By default, the lookup should be performed on the delegate container **only**, not on the container itself.
69 It is however allowed for containers to provide exception cases for special entries, and a way to lookup into
70 the same container (or another container) instead of the delegate container.
74 The *delegate container* will usually be a composite container. A composite container is a container that
75 contains several other containers. When performing a lookup on a composite container, the inner containers are
76 queried until one container returns an entry.
77 An inner container implementing the *delegate lookup feature* will return entries it contains, but if these
78 entries have dependencies, the dependencies lookup calls will be performed on the composite container, giving
79 a chance to all containers to answer.
81 Interestingly enough, the order in which containers are added in the composite container matters. Indeed,
82 the first containers to be added in the composite container can "override" the entries of containers with
85 ![Containers priority](images/priority.png)
87 In the example above, "container 2" contains a controller "myController" and the controller is referencing an
88 "entityManager" entry. "Container 1" contains also an entry named "entityManager".
89 Without the *delegate lookup* feature, when requesting the "myController" instance to container 2, it would take
90 in charge the instanciation of both entries.
92 However, using the *delegate lookup* feature, here is what happens when we ask the composite controller for the
93 "myController" instance:
95 - The composite controller asks container 1 if if contains the "myController" instance. The answer is no.
96 - The composite controller asks container 2 if if contains the "myController" instance. The answer is yes.
97 - The composite controller performs a `get` call on container 2 for the "myController" instance.
98 - Container 2 sees that "myController" has a dependency on "entityManager".
99 - Container 2 delegates the lookup of "entityManager" to the composite controller.
100 - The composite controller asks container 1 if if contains the "entityManager" instance. The answer is yes.
101 - The composite controller performs a `get` call on container 1 for the "entityManager" instance.
103 In the end, we get a controller instanciated by container 2 that references an entityManager instanciated
106 ### 4.3 Alternative: the fallback strategy
108 The first proposed approach we tried was to perform all the lookups in the "local" container,
109 and if a lookup fails in the container, to use the delegate container. In this scenario, the
110 delegate container is used in "fallback" mode.
112 This strategy has been described in @moufmouf blog post: http://mouf-php.com/container-interop-whats-next (solution 1).
113 It was also discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-33570697) and
114 [here](https://github.com/container-interop/container-interop/pull/20#issuecomment-56599631).
116 Problems with this strategy:
118 - Heavy problem regarding infinite loops
119 - Unable to overload a container entry with the delegate container entry
121 ### 4.4 Alternative: force implementing an interface
123 The first proposed approach was to develop a `ParentAwareContainerInterface` interface.
124 It was proposed here: https://github.com/container-interop/container-interop/pull/8
126 The interface would have had the behaviour of the delegate lookup feature but would have forced the addition of
127 a `setParentContainter` method:
130 interface ParentAwareContainerInterface extends ReadableContainerInterface {
132 * Sets the parent container associated to that container. This container will call
133 * the parent container to fetch dependencies.
135 * @param ContainerInterface $container
137 public function setParentContainer(ContainerInterface $container);
141 The interface idea was first questioned by @Ocramius [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777).
142 @Ocramius expressed the idea that an interface should not contain setters, otherwise, it is forcing implementation
143 details on the class implementing the interface.
144 Then @mnapoli made a proposal for a "convention" [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51841079),
145 this idea was further discussed until all participants in the discussion agreed to remove the interface idea
146 and replace it with a "standard" feature.
150 If we had had an interface, we could have delegated the registration of the delegate/composite container to the
151 the delegate/composite container itself.
155 $containerA = new ContainerA();
156 $containerB = new ContainerB();
158 $compositeContainer = new CompositeContainer([$containerA, $containerB]);
160 // The call to 'setParentContainer' is delegated to the CompositeContainer
161 // It is not the responsibility of the user anymore.
162 class CompositeContainer {
165 public function __construct($containers) {
166 foreach ($containers as $container) {
167 if ($container instanceof ParentAwareContainerInterface) {
168 $container->setParentContainer($this);
179 Cons have been extensively discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777).
180 Basically, forcing a setter into an interface is a bad idea. Setters are similar to constructor arguments,
181 and it's a bad idea to standardize a constructor: how the delegate container is configured into a container is an implementation detail. This outweights the benefits of the interface.
183 ### 4.4 Alternative: no exception case for delegate lookups
185 Originally, the proposed wording for delegate lookup calls was:
187 > Important! The lookup MUST be performed on the delegate container **only**, not on the container itself.
189 This was later replaced by:
191 > Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself.
193 > It is however allowed for containers to provide exception cases for special entries, and a way to lookup
194 > into the same container (or another container) instead of the delegate container.
196 Exception cases have been allowed to avoid breaking dependencies with some services that must be provided
197 by the container (on @njasm proposal). This was proposed here: https://github.com/container-interop/container-interop/pull/20#issuecomment-56597235
199 ### 4.5 Alternative: having one of the containers act as the composite container
201 In real-life scenarios, we usually have a big framework (Symfony 2, Zend Framework 2, etc...) and we want to
202 add another DI container to this container. Most of the time, the "big" framework will be responsible for
203 creating the controller's instances, using it's own DI container. Until *container-interop* is fully adopted,
204 the "big" framework will not be aware of the existence of a composite container that it should use instead
205 of its own container.
207 For this real-life use cases, @mnapoli and @moufmouf proposed to extend the "big" framework's DI container
208 to make it act as a composite container.
210 This has been discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-40367194)
211 and [here](http://mouf-php.com/container-interop-whats-next#solution4).
213 This was implemented in Symfony 2 using:
215 - [interop.symfony.di](https://github.com/thecodingmachine/interop.symfony.di/tree/v0.1.0)
216 - [framework interop](https://github.com/mnapoli/framework-interop/)
218 This was implemented in Silex using:
220 - [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di)
222 Having a container act as the composite container is not part of the delegate lookup standard because it is
223 simply a temporary design pattern used to make existing frameworks that do not support yet ContainerInterop
224 play nice with other DI containers.
230 The following projects already implement the delegate lookup feature:
232 - [Mouf](http://mouf-php.com), through the [`setDelegateLookupContainer` method](https://github.com/thecodingmachine/mouf/blob/2.0/src/Mouf/MoufManager.php#L2120)
233 - [PHP-DI](http://php-di.org/), through the [`$wrapperContainer` parameter of the constructor](https://github.com/mnapoli/PHP-DI/blob/master/src/DI/Container.php#L72)
234 - [pimple-interop](https://github.com/moufmouf/pimple-interop), through the [`$container` parameter of the constructor](https://github.com/moufmouf/pimple-interop/blob/master/src/Interop/Container/Pimple/PimpleInterop.php#L62)
239 Are listed here all people that contributed in the discussions, by alphabetical order:
241 - [Alexandru Pătrănescu](https://github.com/drealecs)
242 - [Ben Peachey](https://github.com/potherca)
243 - [David Négrier](https://github.com/moufmouf)
244 - [Jeremy Lindblom](https://github.com/jeremeamia)
245 - [Marco Pivetta](https://github.com/Ocramius)
246 - [Matthieu Napoli](https://github.com/mnapoli)
247 - [Nelson J Morais](https://github.com/njasm)
248 - [Phil Sturgeon](https://github.com/philsturgeon)
249 - [Stephan Hochdörfer](https://github.com/shochdoerfer)
254 _**Note:** Order descending chronologically._
256 - [Pull request on the delegate lookup feature](https://github.com/container-interop/container-interop/pull/20)
257 - [Pull request on the interface idea](https://github.com/container-interop/container-interop/pull/8)
258 - [Original article exposing the delegate lookup idea along many others](http://mouf-php.com/container-interop-whats-next)