Total Complexity | 51 |
Total Lines | 272 |
Duplicated Lines | 0 % |
Changes | 11 | ||
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 | /** @var RouteCollection|array<int,Route> */ |
||
34 | private $routes; |
||
35 | |||
36 | private RouteCompilerInterface $compiler; |
||
37 | |||
38 | private ?RouteGeneratorInterface $compiledData = null; |
||
39 | |||
40 | public function __construct(RouteCollection $collection, RouteCompilerInterface $compiler = null) |
||
41 | { |
||
42 | $this->compiler = $compiler ?? new RouteCompiler(); |
||
43 | $this->routes = $collection; |
||
44 | } |
||
45 | |||
46 | /** |
||
47 | * @internal |
||
48 | */ |
||
49 | public function __serialize(): array |
||
50 | { |
||
51 | return [$this->compiler->build($this->routes), $this->getRoutes(), $this->compiler]; |
||
|
|||
52 | } |
||
53 | |||
54 | /** |
||
55 | * @internal |
||
56 | * |
||
57 | * @param array<int,mixed> $data |
||
58 | */ |
||
59 | public function __unserialize(array $data): void |
||
60 | { |
||
61 | [$this->compiledData, $this->routes, $this->compiler] = $data; |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * {@inheritdoc} |
||
66 | */ |
||
67 | public function matchRequest(ServerRequestInterface $request): ?Route |
||
77 | } |
||
78 | |||
79 | /** |
||
80 | * {@inheritdoc} |
||
81 | */ |
||
82 | public function match(string $method, UriInterface $uri): ?Route |
||
83 | { |
||
84 | if (null === $nextHandler = $this->compiledData) { |
||
85 | return $this->matchCollection($method, $uri, $this->routes); |
||
86 | } |
||
87 | |||
88 | if (\is_array($matchedRoute = $nextHandler->match($method, $uri, \Closure::fromCallable([$this, 'doMatch'])))) { |
||
89 | $requirements = [[], [], []]; |
||
90 | |||
91 | foreach ($matchedRoute as $matchedId) { |
||
92 | $requirements[0] = \array_merge($requirements[0], $this->routes[$matchedId]->getMethods()); |
||
93 | $requirements[1][] = \key($nextHandler->getData()[2][$method][$matchedId] ?? []); |
||
94 | $requirements[2] = \array_merge($requirements[2], $this->routes[$matchedId]->getSchemes()); |
||
95 | } |
||
96 | |||
97 | return $this->assertMatch($method, $uri, $requirements); |
||
98 | } |
||
99 | |||
100 | return $matchedRoute; |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * {@inheritdoc} |
||
105 | */ |
||
106 | public function generateUri(string $routeName, array $parameters = []): GeneratedUri |
||
107 | { |
||
108 | foreach ($this->getRoutes() as $route) { |
||
109 | if ($routeName === $route->getName()) { |
||
110 | return $this->compiler->generateUri($route, $parameters); |
||
111 | } |
||
112 | } |
||
113 | |||
114 | throw new UrlGenerationException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $routeName), 404); |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Get the compiler associated with this class. |
||
119 | */ |
||
120 | public function getCompiler(): RouteCompilerInterface |
||
121 | { |
||
122 | return $this->compiler; |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * Get the routes associated with this class. |
||
127 | * |
||
128 | * @return array<int,Route> |
||
129 | */ |
||
130 | public function getRoutes(): array |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * Tries to match a route from a set of routes. |
||
143 | */ |
||
144 | protected function matchCollection(string $method, UriInterface $uri, RouteCollection $routes): ?Route |
||
145 | { |
||
146 | $requirements = [[], [], []]; |
||
147 | $requestPath = $uri->getPath(); |
||
148 | |||
149 | foreach ($routes->getRoutes() as $route) { |
||
150 | if (!empty($staticPrefix = $route->getStaticPrefix()) && !\str_starts_with($requestPath, $staticPrefix)) { |
||
151 | continue; |
||
152 | } |
||
153 | |||
154 | [$pathRegex, $hostsRegex, $variables] = $this->compiler->compile($route); |
||
155 | |||
156 | if (!\preg_match($pathRegex, $requestPath, $matches, \PREG_UNMATCHED_AS_NULL)) { |
||
157 | continue; |
||
158 | } |
||
159 | |||
160 | $hostsVar = []; |
||
161 | $routeData = $route->getData(); |
||
162 | |||
163 | if (!empty($hostsRegex) && !$this->matchHost($hostsRegex, $uri, $hostsVar)) { |
||
164 | $requirements[1][] = $hostsRegex; |
||
165 | |||
166 | continue; |
||
167 | } |
||
168 | |||
169 | if (!\array_key_exists($method, $routeData['methods'] ?? [])) { |
||
170 | $requirements[0] = \array_merge($requirements[0], $route->getMethods()); |
||
171 | |||
172 | continue; |
||
173 | } |
||
174 | |||
175 | if (isset($routeData['schemes']) && !\array_key_exists($uri->getScheme(), $routeData['schemes'])) { |
||
176 | $requirements[2] = \array_merge($requirements[2], $route->getSchemes()); |
||
177 | |||
178 | continue; |
||
179 | } |
||
180 | |||
181 | if (!empty($variables)) { |
||
182 | $matchInt = 0; |
||
183 | |||
184 | foreach ($variables as $key => $value) { |
||
185 | $route->argument($key, $matches[++$matchInt] ?? $matches[$key] ?? $hostsVar[$key] ?? $value); |
||
186 | } |
||
187 | } |
||
188 | |||
189 | return $route; |
||
190 | } |
||
191 | |||
192 | return $this->assertMatch($method, $uri, $requirements); |
||
193 | } |
||
194 | |||
195 | protected function matchHost(string $hostsRegex, UriInterface $uri, array &$hostsVar): bool |
||
196 | { |
||
197 | $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : ''); |
||
198 | |||
199 | return (bool) \preg_match($hostsRegex, $hostAndPost, $hostsVar, \PREG_UNMATCHED_AS_NULL); |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * @return array<int,mixed> |
||
204 | */ |
||
205 | protected function doMatch(int $matchedId, ?string $domain, UriInterface $uri): array |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * @param array<int,mixed> $requirements |
||
218 | */ |
||
219 | protected function assertMatch(string $method, UriInterface $uri, array $requirements) |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * @param array<int,string> $requiredMethods |
||
240 | */ |
||
241 | protected function assertMethods(string $method, string $uriPath, array $requiredMethods): void |
||
242 | { |
||
243 | $allowedMethods = []; |
||
244 | |||
245 | foreach (\array_unique($requiredMethods) as $requiredMethod) { |
||
246 | if ($method === $requiredMethod || 'HEAD' === $requiredMethod) { |
||
247 | continue; |
||
248 | } |
||
249 | |||
250 | $allowedMethods[] = $requiredMethod; |
||
251 | } |
||
252 | |||
253 | if (!empty($allowedMethods)) { |
||
254 | throw new MethodNotAllowedException($allowedMethods, $uriPath, $method); |
||
255 | } |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * @param array<int,string> $requiredSchemes |
||
260 | */ |
||
261 | protected function assertSchemes(UriInterface $uri, array $requiredSchemes): void |
||
280 | ); |
||
281 | } |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * @param array<int,string> $requiredHosts |
||
286 | */ |
||
287 | protected function assertHosts(UriInterface $uri, array $requiredHosts): void |
||
303 | ); |
||
304 | } |
||
305 | } |
||
307 |