@@ -24,13 +24,14 @@ class RouteHandler
2424 /** @var ErrorHandler */
2525 private $ errorHandler ;
2626
27- /** @var array<string,mixed> */
28- private static $ container = [] ;
27+ /** @var Container */
28+ private $ container ;
2929
30- public function __construct ()
30+ public function __construct (Container $ container = null )
3131 {
3232 $ this ->routeCollector = new RouteCollector (new RouteParser (), new RouteGenerator ());
3333 $ this ->errorHandler = new ErrorHandler ();
34+ $ this ->container = $ container ?? new Container ();
3435 }
3536
3637 /**
@@ -44,12 +45,12 @@ public function map(array $methods, string $route, $handler, ...$handlers): void
4445 if ($ handlers ) {
4546 $ handler = new MiddlewareHandler (array_map (
4647 function ($ handler ) {
47- return is_callable ($ handler ) ? $ handler : self :: callable ($ handler );
48+ return is_callable ($ handler ) ? $ handler : $ this -> container -> callable ($ handler );
4849 },
4950 array_merge ([$ handler ], $ handlers )
5051 ));
5152 } elseif (!is_callable ($ handler )) {
52- $ handler = self :: callable ($ handler );
53+ $ handler = $ this -> container -> callable ($ handler );
5354 }
5455
5556 $ this ->routeDispatcher = null ;
@@ -86,117 +87,4 @@ public function __invoke(ServerRequestInterface $request)
8687 return $ handler ($ request );
8788 }
8889 } // @codeCoverageIgnore
89-
90- /**
91- * @param class-string $class
92- * @return callable
93- */
94- private static function callable ($ class ): callable
95- {
96- return function (ServerRequestInterface $ request , callable $ next = null ) use ($ class ) {
97- // Check `$class` references a valid class name that can be autoloaded
98- if (!\class_exists ($ class , true ) && !interface_exists ($ class , false ) && !trait_exists ($ class , false )) {
99- throw new \BadMethodCallException ('Request handler class ' . $ class . ' not found ' );
100- }
101-
102- try {
103- $ handler = self ::load ($ class );
104- } catch (\Throwable $ e ) {
105- throw new \BadMethodCallException (
106- 'Request handler class ' . $ class . ' failed to load: ' . $ e ->getMessage (),
107- 0 ,
108- $ e
109- );
110- }
111-
112- // Check `$handler` references a class name that is callable, i.e. has an `__invoke()` method.
113- // This initial version is intentionally limited to checking the method name only.
114- // A follow-up version will likely use reflection to check request handler argument types.
115- if (!is_callable ($ handler )) {
116- throw new \BadMethodCallException ('Request handler class " ' . $ class . '" has no public __invoke() method ' );
117- }
118-
119- // invoke request handler as middleware handler or final controller
120- if ($ next === null ) {
121- return $ handler ($ request );
122- }
123- return $ handler ($ request , $ next );
124- };
125- }
126-
127- private static function load (string $ name , int $ depth = 64 )
128- {
129- if (isset (self ::$ container [$ name ])) {
130- return self ::$ container [$ name ];
131- }
132-
133- // Check `$name` references a valid class name that can be autoloaded
134- if (!\class_exists ($ name , true ) && !interface_exists ($ name , false ) && !trait_exists ($ name , false )) {
135- throw new \BadMethodCallException ('Class ' . $ name . ' not found ' );
136- }
137-
138- $ class = new \ReflectionClass ($ name );
139- if (!$ class ->isInstantiable ()) {
140- $ modifier = 'class ' ;
141- if ($ class ->isInterface ()) {
142- $ modifier = 'interface ' ;
143- } elseif ($ class ->isAbstract ()) {
144- $ modifier = 'abstract class ' ;
145- } elseif ($ class ->isTrait ()) {
146- $ modifier = 'trait ' ;
147- }
148- throw new \BadMethodCallException ('Cannot instantiate ' . $ modifier . ' ' . $ name );
149- }
150-
151- // build list of constructor parameters based on parameter types
152- $ params = [];
153- $ ctor = $ class ->getConstructor ();
154- assert ($ ctor === null || $ ctor instanceof \ReflectionMethod);
155- foreach ($ ctor !== null ? $ ctor ->getParameters () : [] as $ parameter ) {
156- assert ($ parameter instanceof \ReflectionParameter);
157-
158- // stop building parameters when encountering first optional parameter
159- if ($ parameter ->isOptional ()) {
160- break ;
161- }
162-
163- // ensure parameter is typed
164- $ type = $ parameter ->getType ();
165- if ($ type === null ) {
166- throw new \BadMethodCallException (self ::parameterError ($ parameter ) . ' has no type ' );
167- }
168-
169- // if allowed, use null value without injecting any instances
170- assert ($ type instanceof \ReflectionType);
171- if ($ type ->allowsNull ()) {
172- $ params [] = null ;
173- continue ;
174- }
175-
176- // abort for union types (PHP 8.0+) and intersection types (PHP 8.1+)
177- if ($ type instanceof \ReflectionUnionType || $ type instanceof \ReflectionIntersectionType) {
178- throw new \BadMethodCallException (self ::parameterError ($ parameter ) . ' expects unsupported type ' . $ type ); // @codeCoverageIgnore
179- }
180-
181- assert ($ type instanceof \ReflectionNamedType);
182- if ($ type ->isBuiltin ()) {
183- throw new \BadMethodCallException (self ::parameterError ($ parameter ) . ' expects unsupported type ' . $ type ->getName ());
184- }
185-
186- // abort for unreasonably deep nesting or recursive types
187- if ($ depth < 1 ) {
188- throw new \BadMethodCallException (self ::parameterError ($ parameter ) . ' is recursive ' );
189- }
190-
191- $ params [] = self ::load ($ type ->getName (), --$ depth );
192- }
193-
194- // instantiate with list of parameters
195- return self ::$ container [$ name ] = $ params === [] ? new $ name () : $ class ->newInstance (...$ params );
196- }
197-
198- private static function parameterError (\ReflectionParameter $ parameter ): string
199- {
200- return 'Argument ' . ($ parameter ->getPosition () + 1 ) . ' ($ ' . $ parameter ->getName () . ') of ' . explode ("\0" , $ parameter ->getDeclaringClass ()->getName ())[0 ] . ':: ' . $ parameter ->getDeclaringFunction ()->getName () . '() ' ;
201- }
20290}
0 commit comments