Total Complexity | 63 |
Total Lines | 397 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like RoutingExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use RoutingExtension, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
49 | class RoutingExtension implements AliasedInterface, BootExtensionInterface, ConfigurationInterface, DependenciesInterface, ExtensionInterface |
||
50 | { |
||
51 | protected const ROUTE_DATA_TO_METHOD = [ |
||
52 | 'name' => 'bind', |
||
53 | 'prefix' => 'prefix', |
||
54 | 'hosts' => 'domain', |
||
55 | 'schemes' => 'scheme', |
||
56 | 'methods' => 'method', |
||
57 | 'defaults' => 'defaults', |
||
58 | 'arguments' => 'arguments', |
||
59 | 'middlewares' => 'piped', |
||
60 | 'patterns' => 'asserts', |
||
61 | 'namespace' => 'namespace', |
||
62 | ]; |
||
63 | |||
64 | /** @var array<int,object> */ |
||
65 | private array $middlewares = []; |
||
66 | |||
67 | private ?string $routeNamespace; |
||
68 | |||
69 | /** |
||
70 | * {@inheritdoc} |
||
71 | */ |
||
72 | public function dependencies(): array |
||
73 | { |
||
74 | return [HttpGalaxyExtension::class]; |
||
|
|||
75 | } |
||
76 | |||
77 | /** |
||
78 | * {@inheritdoc} |
||
79 | */ |
||
80 | public function getAlias(): string |
||
81 | { |
||
82 | return 'routing'; |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * {@inheritdoc} |
||
87 | */ |
||
88 | public function getConfigTreeBuilder(): TreeBuilder |
||
89 | { |
||
90 | $treeBuilder = new TreeBuilder($this->getAlias()); |
||
91 | |||
92 | $treeBuilder->getRootNode() |
||
93 | ->addDefaultsIfNotSet() |
||
94 | ->children() |
||
95 | ->booleanNode('redirect_permanent')->defaultFalse()->end() |
||
96 | ->booleanNode('keep_request_method')->defaultFalse()->end() |
||
97 | ->booleanNode('response_error')->defaultValue(null)->end() |
||
98 | ->booleanNode('resolve_route_paths')->defaultTrue()->end() |
||
99 | ->scalarNode('namespace')->defaultValue(null)->end() |
||
100 | ->scalarNode('routes_handler')->end() |
||
101 | ->scalarNode('cache')->end() |
||
102 | ->arrayNode('middlewares') |
||
103 | ->scalarPrototype()->end() |
||
104 | ->end() |
||
105 | ->arrayNode('pipes') |
||
106 | ->normalizeKeys(false) |
||
107 | ->defaultValue([]) |
||
108 | ->beforeNormalization() |
||
109 | ->ifTrue(fn ($v) => !\is_array($v) || \array_is_list($v)) |
||
110 | ->thenInvalid('Expected patterns values to be an associate array of string keys mapping to mixed values.') |
||
111 | ->end() |
||
112 | ->prototype('variable')->end() |
||
113 | ->end() |
||
114 | ->arrayNode('routes') |
||
115 | ->arrayPrototype() |
||
116 | ->addDefaultsIfNotSet() |
||
117 | ->children() |
||
118 | ->scalarNode('name')->defaultValue(null)->end() |
||
119 | ->scalarNode('path')->isRequired()->end() |
||
120 | ->scalarNode('prefix')->defaultValue(null)->end() |
||
121 | ->scalarNode('to')->defaultValue(null)->end() |
||
122 | ->scalarNode('namespace')->defaultValue(null)->end() |
||
123 | ->booleanNode('debug')->defaultValue(null)->end() |
||
124 | ->arrayNode('methods') |
||
125 | ->beforeNormalization() |
||
126 | ->ifString() |
||
127 | ->then(fn (string $v): array => [$v]) |
||
128 | ->end() |
||
129 | ->defaultValue(Route::DEFAULT_METHODS) |
||
130 | ->prototype('scalar')->end() |
||
131 | ->end() |
||
132 | ->arrayNode('schemes') |
||
133 | ->beforeNormalization() |
||
134 | ->ifString() |
||
135 | ->then(fn (string $v): array => [$v]) |
||
136 | ->end() |
||
137 | ->prototype('scalar')->defaultValue([])->end() |
||
138 | ->end() |
||
139 | ->arrayNode('hosts') |
||
140 | ->beforeNormalization() |
||
141 | ->ifString() |
||
142 | ->then(fn (string $v): array => [$v]) |
||
143 | ->end() |
||
144 | ->prototype('scalar')->defaultValue([])->end() |
||
145 | ->end() |
||
146 | ->arrayNode('middlewares') |
||
147 | ->beforeNormalization() |
||
148 | ->ifString() |
||
149 | ->then(fn (string $v): array => [$v]) |
||
150 | ->end() |
||
151 | ->prototype('scalar')->defaultValue([])->end() |
||
152 | ->end() |
||
153 | ->arrayNode('patterns') |
||
154 | ->normalizeKeys(false) |
||
155 | ->defaultValue([]) |
||
156 | ->beforeNormalization() |
||
157 | ->ifTrue(fn ($v) => !\is_array($v) || \array_is_list($v)) |
||
158 | ->thenInvalid('Expected patterns values to be an associate array of string keys mapping to mixed values.') |
||
159 | ->ifArray() |
||
160 | ->then(fn (array $v): array => $v[0]) |
||
161 | ->end() |
||
162 | ->prototype('variable')->end() |
||
163 | ->end() |
||
164 | ->arrayNode('defaults') |
||
165 | ->normalizeKeys(false) |
||
166 | ->defaultValue([]) |
||
167 | ->beforeNormalization() |
||
168 | ->ifTrue(fn ($v) => !\is_array($v) || \array_is_list($v)) |
||
169 | ->thenInvalid('Expected defaults values to be an associate array of string keys mapping to mixed values.') |
||
170 | ->ifArray() |
||
171 | ->then(fn (array $v): array => $v[0]) |
||
172 | ->end() |
||
173 | ->prototype('variable')->end() |
||
174 | ->end() |
||
175 | ->arrayNode('arguments') |
||
176 | ->normalizeKeys(false) |
||
177 | ->defaultValue([]) |
||
178 | ->beforeNormalization() |
||
179 | ->ifTrue(fn ($v) => !\is_array($v) || \array_is_list($v)) |
||
180 | ->thenInvalid('Expected arguments values to be an associate array of string keys mapping to mixed values.') |
||
181 | ->ifArray() |
||
182 | ->then(fn (array $v): array => $v[0]) |
||
183 | ->end() |
||
184 | ->prototype('variable')->end() |
||
185 | ->end() |
||
186 | ->end() |
||
187 | ->end() |
||
188 | ->end() |
||
189 | ->end() |
||
190 | ; |
||
191 | |||
192 | return $treeBuilder; |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * {@inheritdoc} |
||
197 | */ |
||
198 | public function register(AbstractContainer $container, array $configs = []): void |
||
199 | { |
||
200 | $routeHandler = $configs['routes_handler'] ?? RouteHandler::class; |
||
201 | $this->routeNamespace = $configs['namespace'] ?? null; |
||
202 | |||
203 | if ($container->has($routeHandler)) { |
||
204 | $container->alias(RequestHandlerInterface::class, $routeHandler); |
||
205 | } else { |
||
206 | $container->set(RequestHandlerInterface::class, new Definition($routeHandler)); |
||
207 | } |
||
208 | |||
209 | if ($container->hasExtension(AnnotationExtension::class)) { |
||
210 | $container->autowire('router.annotation_listener', new Definition(Listener::class, [new Statement(RouteCollection::class)])); |
||
211 | } |
||
212 | |||
213 | if (!$container->has('http.router')) { |
||
214 | $container->set('http.router', new Definition(Router::class))->autowire([Router::class, RouteMatcherInterface::class]); |
||
215 | } |
||
216 | |||
217 | $pipesMiddleware = \array_map(static function (array $middlewares) use ($container): array { |
||
218 | foreach ($middlewares as &$middleware) { |
||
219 | if ($container->has($middleware)) { |
||
220 | $middleware = new Reference($middleware); |
||
221 | |||
222 | continue; |
||
223 | } |
||
224 | |||
225 | $middleware = new Statement($middleware); |
||
226 | } |
||
227 | |||
228 | return $middlewares; |
||
229 | }, $configs['pipes']); |
||
230 | $this->middlewares = \array_map(static function (string $middleware) use ($container): object { |
||
231 | if ($container->has($middleware)) { |
||
232 | return new Reference($middleware); |
||
233 | } |
||
234 | |||
235 | return new Statement($middleware); |
||
236 | }, $configs['middlewares']); |
||
237 | |||
238 | $router = $container->definition('http.router'); |
||
239 | $this->middlewares[] = new Reference('http.middleware.headers'); |
||
240 | |||
241 | if ($configs['resolve_route_paths']) { |
||
242 | $this->middlewares[] = new Statement(PathMiddleware::class, [$configs['redirect_permanent'], $configs['keep_request_method']]); |
||
243 | } |
||
244 | |||
245 | if ($container->has('http.middleware.cookie')) { |
||
246 | $this->middlewares[] = new Reference('http.middleware.cookie'); |
||
247 | } |
||
248 | |||
249 | if ($container->has('http.middleware.policies')) { |
||
250 | $this->middlewares[] = new Reference('http.middleware.policies'); |
||
251 | } |
||
252 | |||
253 | if ($container->has('http.middleware.cors')) { |
||
254 | $this->middlewares[] = new Reference('http.middleware.cors'); |
||
255 | } |
||
256 | |||
257 | if (isset($configs['response_error'])) { |
||
258 | $this->middlewares[] = new Statement(ErrorHandlerMiddleware::class, [$configs['response_error']]); |
||
259 | } |
||
260 | |||
261 | if ($container->has('http.middleware.cache')) { |
||
262 | $this->middlewares[] = new Reference('http.middleware.cache'); |
||
263 | } |
||
264 | |||
265 | if ($router instanceof Router) { |
||
266 | if (!$container instanceof Container) { |
||
267 | throw new ServiceCreationException(\sprintf('Constructing a "%s" instance requires non-builder container.', Router::class)); |
||
268 | } |
||
269 | |||
270 | foreach ($pipesMiddleware as $middlewareId => $middlewares) { |
||
271 | $router->pipes($middlewareId, ...$container->getResolver()->resolveArguments($middlewares)); |
||
272 | } |
||
273 | } else { |
||
274 | foreach ($pipesMiddleware as $middlewareId => $middlewares) { |
||
275 | $router->bind('pipes', [$middlewareId, $middlewares]); |
||
276 | } |
||
277 | |||
278 | if ($configs['cache']) { |
||
279 | $router->arg(1, $container->has($configs['cache']) ? new Reference($configs['cache']) : $configs['cache']); |
||
280 | } |
||
281 | } |
||
282 | |||
283 | $container->parameters['routes'] = \array_merge($configs['routes'] ?? [], $container->parameters['routes'] ?? []); |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * {@inheritdoc} |
||
288 | */ |
||
289 | public function boot(AbstractContainer $container): void |
||
290 | { |
||
291 | [$collection, $groups] = $this->booRoutes($container, $this->routeNamespace); |
||
292 | |||
293 | $routes = $container->findBy(RouteCollection::class, static function (string $routesId) use ($container, $groups) { |
||
294 | if (!empty($collection = $groups[$routesId] ?? [])) { |
||
295 | $grouped = $container->definition($routesId); |
||
296 | |||
297 | if ($grouped instanceof RouteCollection) { |
||
298 | if (!$container instanceof Container) { |
||
299 | throw new ServiceCreationException(\sprintf('Constructing a "%s" instance requires non-builder container.', RouteCollection::class)); |
||
300 | } |
||
301 | |||
302 | return $grouped->prototype($collection)->end(); |
||
303 | } |
||
304 | |||
305 | $grouped->bind('prototype', [$collection]); |
||
306 | } |
||
307 | |||
308 | return new Reference($routesId); |
||
309 | }); |
||
310 | $middlewares = $container->findBy(MiddlewareInterface::class, function (string $middlewareId) use ($container) { |
||
311 | $middleware = $container->definition($middlewareId); |
||
312 | |||
313 | if ($middleware instanceof MiddlewareInterface) { |
||
314 | if (!$container instanceof Container) { |
||
315 | throw new ServiceCreationException(\sprintf('Constructing a "%s" instance requires non-builder container.', MiddlewareInterface::class)); |
||
316 | } |
||
317 | |||
318 | return $middleware; |
||
319 | } |
||
320 | |||
321 | return new Reference($middlewareId); |
||
322 | }); |
||
323 | |||
324 | $router = $container->definition('http.router'); |
||
325 | $middlewares = \array_merge($middlewares, $this->middlewares); |
||
326 | |||
327 | if ($router instanceof Router) { |
||
328 | if (!$container instanceof Container) { |
||
329 | throw new ServiceCreationException(\sprintf('Constructing a "%s" instance requires non-builder container.', Router::class)); |
||
330 | } |
||
331 | |||
332 | $router->pipe(...$container->getResolver()->resolveArguments($middlewares)); |
||
333 | |||
334 | if (!empty($collection)) { |
||
335 | $router->getCollection()->routes($collection[0]); |
||
336 | } |
||
337 | |||
338 | foreach ($routes as $group) { |
||
339 | $router->getCollection()->populate($group instanceof Reference ? $container->get((string) $group) : $group, true); |
||
340 | $container->removeDefinition((string) $group); |
||
341 | } |
||
342 | |||
343 | $container->removeType(RouteCollection::class); |
||
344 | } else { |
||
345 | if ($container instanceof Container) { |
||
346 | throw new ServiceCreationException(\sprintf('Constructing a "%s" instance requires a builder container.', Router::class)); |
||
347 | } |
||
348 | |||
349 | $router->bind('pipe', [$middlewares]); |
||
350 | $groupedCollection = 'function (\Flight\Routing\RouteCollection $collection) {'; |
||
351 | |||
352 | if (!empty($collection)) { |
||
353 | $groupedCollection .= '$collection->routes(\'??\');'; |
||
354 | } |
||
355 | |||
356 | foreach ($routes as $group) { |
||
357 | $groupedCollection .= '$collection->populate(\'??\', true);'; |
||
358 | $collection[] = $group; |
||
359 | } |
||
360 | |||
361 | $router->bind('setCollection', new PhpLiteral($groupedCollection . '};', $collection)); |
||
362 | } |
||
363 | |||
364 | unset($container->parameters['routes']); |
||
365 | } |
||
366 | |||
367 | public function booRoutes(AbstractContainer $container, ?string $routeNamespace): array |
||
446 | } |
||
447 | } |
||
448 |
In the issue above, the returned value is violating the contract defined by the mentioned interface.
Let's take a look at an example: