1010 */
1111class Container
1212{
13- /** @var array<class- string,object|callable():(object|class- string)>|ContainerInterface */
13+ /** @var array<string,object|callable():(object|string)|string >|ContainerInterface */
1414 private $ container ;
1515
16- /** @var array<class- string,callable():(object|class- string) | object | class- string>|ContainerInterface $loader */
16+ /** @var array<string,callable():(object|string) | object | string>|ContainerInterface $loader */
1717 public function __construct ($ loader = [])
1818 {
1919 if (!\is_array ($ loader ) && !$ loader instanceof ContainerInterface) {
@@ -23,11 +23,7 @@ public function __construct($loader = [])
2323 }
2424
2525 foreach (($ loader instanceof ContainerInterface ? [] : $ loader ) as $ name => $ value ) {
26- if (\is_string ($ value )) {
27- $ loader [$ name ] = static function () use ($ value ) {
28- return $ value ;
29- };
30- } elseif (!$ value instanceof \Closure && !$ value instanceof $ name ) {
26+ if (!\is_string ($ value ) && !$ value instanceof \Closure && !$ value instanceof $ name ) {
3127 throw new \BadMethodCallException ('Map for ' . $ name . ' contains unexpected ' . (is_object ($ value ) ? get_class ($ value ) : gettype ($ value )));
3228 }
3329 }
@@ -67,7 +63,7 @@ public function callable(string $class): callable
6763 if ($ this ->container instanceof ContainerInterface) {
6864 $ handler = $ this ->container ->get ($ class );
6965 } else {
70- $ handler = $ this ->load ($ class );
66+ $ handler = $ this ->loadObject ($ class );
7167 }
7268 } catch (\Throwable $ e ) {
7369 throw new \BadMethodCallException (
@@ -102,7 +98,7 @@ public function getAccessLogHandler(): AccessLogHandler
10298 return new AccessLogHandler ();
10399 }
104100 }
105- return $ this ->load (AccessLogHandler::class);
101+ return $ this ->loadObject (AccessLogHandler::class);
106102 }
107103
108104 /** @internal */
@@ -115,21 +111,33 @@ public function getErrorHandler(): ErrorHandler
115111 return new ErrorHandler ();
116112 }
117113 }
118- return $ this ->load (ErrorHandler::class);
114+ return $ this ->loadObject (ErrorHandler::class);
119115 }
120116
121117 /**
122- * @param class-string $name
123- * @return object
124- * @throws \BadMethodCallException
118+ * @template T
119+ * @param class-string<T> $name
120+ * @return T
121+ * @throws \BadMethodCallException if object of type $name can not be loaded
125122 */
126- private function load (string $ name , int $ depth = 64 )
123+ private function loadObject (string $ name , int $ depth = 64 ) /*: object (PHP 7.2+) */
127124 {
128125 if (isset ($ this ->container [$ name ])) {
129- if ($ this ->container [$ name ] instanceof \Closure) {
126+ if (\is_string ($ this ->container [$ name ])) {
127+ if ($ depth < 1 ) {
128+ throw new \BadMethodCallException ('Factory for ' . $ name . ' is recursive ' );
129+ }
130+
131+ $ value = $ this ->loadObject ($ this ->container [$ name ], $ depth - 1 );
132+ if (!$ value instanceof $ name ) {
133+ throw new \BadMethodCallException ('Factory for ' . $ name . ' returned unexpected ' . (is_object ($ value ) ? get_class ($ value ) : gettype ($ value )));
134+ }
135+
136+ $ this ->container [$ name ] = $ value ;
137+ } elseif ($ this ->container [$ name ] instanceof \Closure) {
130138 // build list of factory parameters based on parameter types
131139 $ closure = new \ReflectionFunction ($ this ->container [$ name ]);
132- $ params = $ this ->loadFunctionParams ($ closure , $ depth );
140+ $ params = $ this ->loadFunctionParams ($ closure , $ depth, true );
133141
134142 // invoke factory with list of parameters
135143 $ value = $ params === [] ? ($ this ->container [$ name ])() : ($ this ->container [$ name ])(...$ params );
@@ -139,14 +147,17 @@ private function load(string $name, int $depth = 64)
139147 throw new \BadMethodCallException ('Factory for ' . $ name . ' is recursive ' );
140148 }
141149
142- $ value = $ this ->load ($ value , $ depth - 1 );
143- } elseif (!$ value instanceof $ name ) {
150+ $ value = $ this ->loadObject ($ value , $ depth - 1 );
151+ }
152+ if (!$ value instanceof $ name ) {
144153 throw new \BadMethodCallException ('Factory for ' . $ name . ' returned unexpected ' . (is_object ($ value ) ? get_class ($ value ) : gettype ($ value )));
145154 }
146155
147156 $ this ->container [$ name ] = $ value ;
148157 }
149158
159+ assert ($ this ->container [$ name ] instanceof $ name );
160+
150161 return $ this ->container [$ name ];
151162 }
152163
@@ -170,13 +181,14 @@ private function load(string $name, int $depth = 64)
170181
171182 // build list of constructor parameters based on parameter types
172183 $ ctor = $ class ->getConstructor ();
173- $ params = $ ctor === null ? [] : $ this ->loadFunctionParams ($ ctor , $ depth );
184+ $ params = $ ctor === null ? [] : $ this ->loadFunctionParams ($ ctor , $ depth, false );
174185
175186 // instantiate with list of parameters
176187 return $ this ->container [$ name ] = $ params === [] ? new $ name () : $ class ->newInstance (...$ params );
177188 }
178189
179- private function loadFunctionParams (\ReflectionFunctionAbstract $ function , int $ depth ): array
190+ /** @throws \BadMethodCallException if either parameter can not be loaded */
191+ private function loadFunctionParams (\ReflectionFunctionAbstract $ function , int $ depth , bool $ allowVariables ): array
180192 {
181193 $ params = [];
182194 foreach ($ function ->getParameters () as $ parameter ) {
@@ -206,6 +218,14 @@ private function loadFunctionParams(\ReflectionFunctionAbstract $function, int $
206218 }
207219
208220 assert ($ type instanceof \ReflectionNamedType);
221+
222+ // load string variables from container
223+ if ($ allowVariables && $ type ->getName () === 'string ' ) {
224+ $ params [] = $ this ->loadVariable ($ parameter ->getName (), $ depth );
225+ continue ;
226+ }
227+
228+ // abort for other primitive types
209229 if ($ type ->isBuiltin ()) {
210230 throw new \BadMethodCallException (self ::parameterError ($ parameter ) . ' expects unsupported type ' . $ type ->getName ());
211231 }
@@ -215,12 +235,47 @@ private function loadFunctionParams(\ReflectionFunctionAbstract $function, int $
215235 throw new \BadMethodCallException (self ::parameterError ($ parameter ) . ' is recursive ' );
216236 }
217237
218- $ params [] = $ this ->load ($ type ->getName (), $ depth - 1 );
238+ $ params [] = $ this ->loadObject ($ type ->getName (), $ depth - 1 );
219239 }
220240
221241 return $ params ;
222242 }
223243
244+ /** @throws \BadMethodCallException if $name is not a valid string variable */
245+ private function loadVariable (string $ name , int $ depth ): string
246+ {
247+ if (!isset ($ this ->container [$ name ])) {
248+ throw new \BadMethodCallException ('Container variable $ ' . $ name . ' is not defined ' );
249+ }
250+
251+ if ($ this ->container [$ name ] instanceof \Closure) {
252+ if ($ depth < 1 ) {
253+ throw new \BadMethodCallException ('Container variable $ ' . $ name . ' is recursive ' );
254+ }
255+
256+ // build list of factory parameters based on parameter types
257+ $ closure = new \ReflectionFunction ($ this ->container [$ name ]);
258+ $ params = $ this ->loadFunctionParams ($ closure , $ depth - 1 , true );
259+
260+ // invoke factory with list of parameters
261+ $ value = $ params === [] ? ($ this ->container [$ name ])() : ($ this ->container [$ name ])(...$ params );
262+
263+ if (!\is_string ($ value )) {
264+ throw new \BadMethodCallException ('Container variable $ ' . $ name . ' expected type string from factory, but got ' . (\is_object ($ value ) ? \get_class ($ value ) : \gettype ($ value )));
265+ }
266+
267+ $ this ->container [$ name ] = $ value ;
268+ }
269+
270+ $ value = $ this ->container [$ name ];
271+ if (!\is_string ($ value )) {
272+ throw new \BadMethodCallException ('Container variable $ ' . $ name . ' expected type string, but got ' . (\is_object ($ value ) ? \get_class ($ value ) : \gettype ($ value )));
273+ }
274+
275+ return $ value ;
276+ }
277+
278+ /** @throws void */
224279 private static function parameterError (\ReflectionParameter $ parameter ): string
225280 {
226281 $ name = $ parameter ->getDeclaringFunction ()->getShortName ();
0 commit comments