| Total Complexity | 69 |
| Total Lines | 371 |
| Duplicated Lines | 0 % |
| Changes | 13 | ||
| Bugs | 1 | Features | 0 |
Complex classes like RouteMatcher 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 RouteMatcher, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 31 | class RouteMatcher implements RouteMatcherInterface |
||
| 32 | { |
||
| 33 | private RouteCollection $routes; |
||
| 34 | private RouteCompilerInterface $compiler; |
||
| 35 | |||
| 36 | /** @var array<int|string,mixed> Optimize compiled routes, generated route uri and hosts */ |
||
| 37 | private array $optimized = []; |
||
| 38 | |||
| 39 | /** @var array<int|string,mixed> */ |
||
| 40 | private ?array $compiledData = null; |
||
| 41 | |||
| 42 | /** @var callable|null */ |
||
| 43 | private $cacheResolver; |
||
| 44 | |||
| 45 | public function __construct(RouteCollection $collection, RouteCompilerInterface $compiler = null) |
||
| 46 | { |
||
| 47 | $this->compiler = $compiler ?? new RouteCompiler(); |
||
| 48 | $this->routes = $collection; |
||
| 49 | } |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @internal |
||
| 53 | */ |
||
| 54 | public function __serialize(): array |
||
| 55 | { |
||
| 56 | return [$this->compiler->build($this->routes), $this->getRoutes(), $this->compiler]; |
||
| 57 | } |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @internal |
||
| 61 | * |
||
| 62 | * @param array<int,mixed> $data |
||
| 63 | */ |
||
| 64 | public function __unserialize(array $data): void |
||
| 65 | { |
||
| 66 | if (!empty($compiled = $data[0])) { |
||
| 67 | $this->compiledData = $compiled; |
||
| 68 | $this->optimized = $compiled[3] ?? []; |
||
| 69 | $this->cacheResolver = $compiled['handler'] ?? [$this, 'matchCached']; |
||
| 70 | |||
| 71 | unset($compiled[3]); |
||
| 72 | } |
||
| 73 | $this->routes = RouteCollection::create($data[1]); |
||
| 74 | $this->compiler = $data[2]; |
||
| 75 | } |
||
| 76 | |||
| 77 | /** |
||
| 78 | * @internal |
||
| 79 | * |
||
| 80 | * @param array<string,mixed> $properties The route data properties |
||
| 81 | * |
||
| 82 | * @return static |
||
| 83 | */ |
||
| 84 | public static function __set_state(array $properties) |
||
| 85 | { |
||
| 86 | $matcher = new static(RouteCollection::create($properties['routes']), $properties['compiler'] ?? null); |
||
| 87 | |||
| 88 | if (!empty($compiled = $properties['compiled'] ?? [])) { |
||
| 89 | $matcher->compiledData = $compiled; |
||
| 90 | $matcher->optimized = $compiled[3] ?? []; |
||
| 91 | $matcher->cacheResolver = $compiled['handler'] ?? [$matcher, 'matchCached']; |
||
| 92 | |||
| 93 | unset($compiled[3]); |
||
| 94 | } |
||
| 95 | |||
| 96 | return $matcher; |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * {@inheritdoc} |
||
| 101 | */ |
||
| 102 | public function matchRequest(ServerRequestInterface $request): ?Route |
||
| 112 | } |
||
| 113 | |||
| 114 | /** |
||
| 115 | * {@inheritdoc} |
||
| 116 | */ |
||
| 117 | public function match(string $method, UriInterface $uri): ?Route |
||
| 118 | { |
||
| 119 | $matchedRoute = ($this->cacheResolver ?? [$this, 'matchCollection'])($method, $uri, $this->compiledData ?? $this->routes); |
||
| 120 | |||
| 121 | if (\is_array($matchedRoute)) { |
||
| 122 | $requirements = [[], [], []]; |
||
| 123 | |||
| 124 | foreach ($matchedRoute as $matchedId) { |
||
| 125 | $matchedRoute = $this->routes->getRoutes()[$matchedId] ?? null; |
||
| 126 | |||
| 127 | if (null !== $matchedRoute) { |
||
| 128 | $requirements[0] = \array_merge($requirements[0], $matchedRoute->getMethods()); |
||
| 129 | $requirements[2] = \array_merge($requirements[2], $matchedRoute->getSchemes()); |
||
| 130 | |||
| 131 | if (isset($this->compiledData[2][$matchedId][0])) { |
||
| 132 | $requirements[1][] = $this->compiledData[2][$matchedId][0]; |
||
| 133 | } |
||
| 134 | } |
||
| 135 | } |
||
| 136 | |||
| 137 | return $this->assertMatch($method, $uri, $requirements); |
||
|
|
|||
| 138 | } |
||
| 139 | |||
| 140 | return $matchedRoute; |
||
| 141 | } |
||
| 142 | |||
| 143 | /** |
||
| 144 | * {@inheritdoc} |
||
| 145 | */ |
||
| 146 | public function generateUri(string $routeName, array $parameters = []): GeneratedUri |
||
| 147 | { |
||
| 148 | if (null === $optimized = &$this->optimized[$routeName] ?? null) { |
||
| 149 | if (null === $this->compiledData) { |
||
| 150 | foreach ($this->routes->getRoutes() as $offset => $route) { |
||
| 151 | if ($routeName === $route->getName()) { |
||
| 152 | $optimized = $offset; |
||
| 153 | goto generate_uri; |
||
| 154 | } |
||
| 155 | } |
||
| 156 | } |
||
| 157 | |||
| 158 | throw new UrlGenerationException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $routeName), 404); |
||
| 159 | } |
||
| 160 | |||
| 161 | generate_uri: |
||
| 162 | return $this->compiler->generateUri($this->routes->getRoutes()[$optimized], $parameters); |
||
| 163 | } |
||
| 164 | |||
| 165 | /** |
||
| 166 | * Get the compiler associated with this class. |
||
| 167 | */ |
||
| 168 | public function getCompiler(): RouteCompilerInterface |
||
| 169 | { |
||
| 170 | return $this->compiler; |
||
| 171 | } |
||
| 172 | |||
| 173 | /** |
||
| 174 | * Get the routes associated with this class. |
||
| 175 | * |
||
| 176 | * @return array<int,Route> |
||
| 177 | */ |
||
| 178 | public function getRoutes(): array |
||
| 181 | } |
||
| 182 | |||
| 183 | /** |
||
| 184 | * Tries to match a route from a set of routes. |
||
| 185 | */ |
||
| 186 | protected function matchCollection(string $method, UriInterface $uri, RouteCollection $routes): ?Route |
||
| 187 | { |
||
| 188 | $requestPath = $uri->getPath(); |
||
| 189 | $requirements = [[], [], []]; |
||
| 190 | |||
| 191 | foreach ($routes->getRoutes() as $offset => $route) { |
||
| 192 | if (!empty($staticPrefix = $route->getStaticPrefix()) && !\str_starts_with($requestPath, $staticPrefix)) { |
||
| 193 | continue; |
||
| 194 | } |
||
| 195 | [$pathRegex, $hostsRegex, $variables] = $this->optimized[$offset] ??= $this->compiler->compile($route); |
||
| 196 | |||
| 197 | if (!\preg_match($pathRegex, $requestPath, $matches, \PREG_UNMATCHED_AS_NULL)) { |
||
| 198 | continue; |
||
| 199 | } |
||
| 200 | |||
| 201 | $hostsVar = []; |
||
| 202 | $requiredSchemes = $route->getSchemes(); |
||
| 203 | |||
| 204 | if ($hostsRegex && !$this->matchHost($hostsRegex, $uri, $hostsVar)) { |
||
| 205 | $requirements[1][] = $hostsRegex; |
||
| 206 | |||
| 207 | continue; |
||
| 208 | } |
||
| 209 | |||
| 210 | if (!\in_array($method, $route->getMethods(), true)) { |
||
| 211 | $requirements[0] = \array_merge($requirements[0], $route->getMethods()); |
||
| 212 | |||
| 213 | continue; |
||
| 214 | } |
||
| 215 | |||
| 216 | if ($requiredSchemes && !\in_array($uri->getScheme(), $requiredSchemes, true)) { |
||
| 217 | $requirements[2] = \array_merge($requirements[2], $requiredSchemes); |
||
| 218 | |||
| 219 | continue; |
||
| 220 | } |
||
| 221 | |||
| 222 | if (!empty($variables)) { |
||
| 223 | $matchInt = 0; |
||
| 224 | |||
| 225 | foreach ($variables as $key => $value) { |
||
| 226 | $route->argument($key, $matches[++$matchInt] ?? $matches[$key] ?? $hostsVar[$key] ?? $value); |
||
| 227 | } |
||
| 228 | } |
||
| 229 | |||
| 230 | return $route; |
||
| 231 | } |
||
| 232 | |||
| 233 | return $this->assertMatch($method, $uri, $requirements); |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Tries matching routes from cache. |
||
| 238 | */ |
||
| 239 | public function matchCached(string $method, UriInterface $uri, array $routes): ?Route |
||
| 296 | } |
||
| 297 | |||
| 298 | protected function matchHost(string $hostsRegex, UriInterface $uri, array &$hostsVar): bool |
||
| 299 | { |
||
| 300 | $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : ''); |
||
| 313 | } |
||
| 314 | |||
| 315 | /** |
||
| 316 | * @param array<int,mixed> $requirements |
||
| 317 | */ |
||
| 318 | protected function assertMatch(string $method, UriInterface $uri, array $requirements) |
||
| 335 | } |
||
| 336 | |||
| 337 | /** |
||
| 338 | * @param array<int,string> $requiredMethods |
||
| 339 | */ |
||
| 340 | protected function assertMethods(string $method, string $uriPath, array $requiredMethods): void |
||
| 341 | { |
||
| 342 | $allowedMethods = []; |
||
| 343 | |||
| 344 | foreach (\array_unique($requiredMethods) as $requiredMethod) { |
||
| 345 | if ($method === $requiredMethod || 'HEAD' === $requiredMethod) { |
||
| 346 | continue; |
||
| 347 | } |
||
| 348 | |||
| 349 | $allowedMethods[] = $requiredMethod; |
||
| 350 | } |
||
| 351 | |||
| 352 | if (!empty($allowedMethods)) { |
||
| 353 | throw new MethodNotAllowedException($allowedMethods, $uriPath, $method); |
||
| 354 | } |
||
| 355 | } |
||
| 356 | |||
| 357 | /** |
||
| 358 | * @param array<int,string> $requiredSchemes |
||
| 359 | */ |
||
| 360 | protected function assertSchemes(UriInterface $uri, array $requiredSchemes): void |
||
| 379 | ); |
||
| 380 | } |
||
| 381 | } |
||
| 382 | |||
| 383 | /** |
||
| 384 | * @param array<int,string> $requiredHosts |
||
| 385 | */ |
||
| 386 | protected function assertHosts(UriInterface $uri, array $requiredHosts): void |
||
| 402 | ); |
||
| 403 | } |
||
| 404 | } |
||
| 405 | } |
||
| 406 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.