Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
192 / 192 |
|
100.00% |
9 / 9 |
CRAP | |
100.00% |
1 / 1 |
RoutingCollector | |
100.00% |
192 / 192 |
|
100.00% |
9 / 9 |
49 | |
100.00% |
1 / 1 |
setRouter | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getActivities | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
5 | |||
getContents | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
8 | |||
renderMatchedRoute | |
100.00% |
47 / 47 |
|
100.00% |
1 / 1 |
10 | |||
renderRouteCollections | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
8 | |||
renderRouteCollectionTime | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
renderRouteCollectionsTable | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
7 | |||
getRoutes | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
5 | |||
toCodeBrackets | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 |
1 | <?php declare(strict_types=1); |
2 | /* |
3 | * This file is part of Aplus Framework Routing Library. |
4 | * |
5 | * (c) Natan Felles <natanfelles@gmail.com> |
6 | * |
7 | * For the full copyright and license information, please view the LICENSE |
8 | * file that was distributed with this source code. |
9 | */ |
10 | namespace Framework\Routing\Debug; |
11 | |
12 | use Closure; |
13 | use Framework\Debug\Collector; |
14 | use Framework\Routing\RouteCollection; |
15 | use Framework\Routing\Router; |
16 | |
17 | /** |
18 | * Class RoutingCollector. |
19 | * |
20 | * @package routing |
21 | */ |
22 | class RoutingCollector extends Collector |
23 | { |
24 | protected Router $router; |
25 | |
26 | public function setRouter(Router $router) : static |
27 | { |
28 | $this->router = $router; |
29 | return $this; |
30 | } |
31 | |
32 | public function getActivities() : array |
33 | { |
34 | $activities = []; |
35 | $serveCount = 0; |
36 | foreach ($this->getData() as $data) { |
37 | if ($data['type'] === 'serve') { |
38 | $serveCount++; |
39 | $activities[] = [ |
40 | 'collector' => $this->getName(), |
41 | 'class' => static::class, |
42 | 'description' => 'Serve route collection ' . $serveCount, |
43 | 'start' => $data['start'], |
44 | 'end' => $data['end'], |
45 | ]; |
46 | } elseif ($data['type'] === 'match') { |
47 | $activities[] = [ |
48 | 'collector' => $this->getName(), |
49 | 'class' => static::class, |
50 | 'description' => 'Match route', |
51 | 'start' => $data['start'], |
52 | 'end' => $data['end'], |
53 | ]; |
54 | } elseif ($data['type'] === 'run') { |
55 | $activities[] = [ |
56 | 'collector' => $this->getName(), |
57 | 'class' => static::class, |
58 | 'description' => 'Run matched route', |
59 | 'start' => $data['start'], |
60 | 'end' => $data['end'], |
61 | ]; |
62 | } |
63 | } |
64 | return $activities; |
65 | } |
66 | |
67 | public function getContents() : string |
68 | { |
69 | if ( ! isset($this->router)) { |
70 | return '<p>A Router instance has not been set on this collector.</p>'; |
71 | } |
72 | \ob_start(); ?> |
73 | <h1>Matched Route</h1> |
74 | <?= $this->renderMatchedRoute() ?> |
75 | <h1>Route Collections</h1> |
76 | <?= $this->renderRouteCollections() ?> |
77 | <h1>Router Infos</h1> |
78 | <p><strong>Auto Methods:</strong> <?= $this->router->isAutoMethods() ? 'On' : 'Off' ?></p> |
79 | <p><strong>Auto Options:</strong> <?= $this->router->isAutoOptions() ? 'On' : 'Off' ?></p> |
80 | <p> |
81 | <strong>Default Route Action Method:</strong> <?= \htmlentities($this->router->getDefaultRouteActionMethod()) ?> |
82 | </p> |
83 | <?php |
84 | $notFound = $this->router->defaultRouteNotFound; // @phpstan-ignore-line |
85 | if ($notFound): ?> |
86 | <p><strong>Default Route Not Found:</strong> <?= |
87 | $notFound instanceof Closure ? 'Closure' : \htmlentities($notFound) |
88 | ?></p> |
89 | <?php |
90 | endif ?> |
91 | <h2>Placeholders</h2> |
92 | <?php |
93 | $placeholders = []; |
94 | foreach ($this->router->getPlaceholders() as $placeholder => $pattern) { |
95 | $placeholders[\trim($placeholder, '{}')] = $pattern; |
96 | } |
97 | \ksort($placeholders); ?> |
98 | <p>Total of <?= \count($placeholders) ?> placeholders.</p> |
99 | <table> |
100 | <thead> |
101 | <tr> |
102 | <th>Placeholder</th> |
103 | <th>Pattern</th> |
104 | </tr> |
105 | </thead> |
106 | <tbody> |
107 | <?php foreach ($placeholders as $placeholder => $pattern): ?> |
108 | <tr> |
109 | <td><code>{<?= \htmlentities($placeholder) ?>}</code></td> |
110 | <td> |
111 | <pre><code class="language-regex"><?= \htmlentities($pattern) ?></code></pre> |
112 | </td> |
113 | </tr> |
114 | <?php endforeach ?> |
115 | </tbody> |
116 | </table> |
117 | <?php |
118 | return \ob_get_clean(); // @phpstan-ignore-line |
119 | } |
120 | |
121 | protected function renderMatchedRoute() : string |
122 | { |
123 | $route = $this->router->getMatchedRoute(); |
124 | if ($route === null) { |
125 | return '<p>No matching route on this Router instance.</p>'; |
126 | } |
127 | \ob_start(); ?> |
128 | <table> |
129 | <thead> |
130 | <tr> |
131 | <th title="Route Collection">RC</th> |
132 | <th>Method</th> |
133 | <th>Origin</th> |
134 | <th>Path</th> |
135 | <th>Action</th> |
136 | <th>Name</th> |
137 | <th>Has Options</th> |
138 | <th title="Seconds">Time to Match</th> |
139 | <th title="Seconds">Runtime</th> |
140 | </tr> |
141 | </thead> |
142 | <tbody> |
143 | <tr> |
144 | <td><?php |
145 | foreach ($this->router->getCollections() as $index => $collection) { |
146 | if ($collection === $this->router->getMatchedCollection()) { |
147 | echo $index + 1; |
148 | } |
149 | } ?></td> |
150 | <td><?= $this->router->getResponse()->getRequest()->getMethod() ?></td> |
151 | |
152 | <td><?= \htmlentities($this->router->getMatchedOrigin()) ?></td> |
153 | <td><?= \htmlentities($this->router->getMatchedPath()) ?></td> |
154 | <td><?= $route->getAction() instanceof Closure |
155 | ? 'Closure' |
156 | : \htmlentities($route->getAction()) ?></td> |
157 | <td><?= \htmlentities((string) $route->getName()) ?></td> |
158 | <td><?= $route->getOptions() ? 'Yes' : 'No' ?></td> |
159 | <td><?php |
160 | foreach ($this->getData() as $data) { |
161 | if ($data['type'] === 'match') { |
162 | echo \round($data['end'] - $data['start'], 6); |
163 | } |
164 | } ?></td> |
165 | <td><?php |
166 | foreach ($this->getData() as $data) { |
167 | if ($data['type'] === 'run') { |
168 | echo \round($data['end'] - $data['start'], 6); |
169 | } |
170 | } ?></td> |
171 | </tr> |
172 | </tbody> |
173 | </table> |
174 | <?php |
175 | return \ob_get_clean(); // @phpstan-ignore-line |
176 | } |
177 | |
178 | protected function renderRouteCollections() : string |
179 | { |
180 | $countCollections = \count($this->router->getCollections()); |
181 | if ($countCollections === 0) { |
182 | return '<p>No route collection has been set.</p>'; |
183 | } |
184 | $plural = $countCollections > 1; |
185 | \ob_start(); ?> |
186 | <p>There <?= $plural ? 'are' : 'is' ?> <?= $countCollections ?> route collection<?= |
187 | $plural ? 's' : '' ?> set. |
188 | </p> |
189 | <?php |
190 | foreach ($this->router->getCollections() as $index => $collection): ?> |
191 | <h2>Route Collection <?= $index + 1 ?></h2> |
192 | <p><strong>Origin:</strong> <?= $this->toCodeBrackets($collection->origin) ?></p> |
193 | <?php |
194 | if ($collection->name !== null): ?> |
195 | <p><strong>Name:</strong> <?= $collection->name ?></p> |
196 | <?php |
197 | endif; |
198 | $notFound = $collection->notFoundAction ?? null; |
199 | if ($notFound !== null): |
200 | ?> |
201 | <p><strong>Route Not Found:</strong> <?= $notFound instanceof Closure |
202 | ? 'Closure' |
203 | : \htmlentities($notFound) ?></p> |
204 | <?php |
205 | endif; |
206 | echo $this->renderRouteCollectionsTable($collection); |
207 | endforeach; |
208 | return \ob_get_clean(); // @phpstan-ignore-line |
209 | } |
210 | |
211 | protected function renderRouteCollectionTime(RouteCollection $collection) : string |
212 | { |
213 | $contents = ''; |
214 | foreach ($this->getData() as $data) { |
215 | if ($data['type'] === 'serve' && $data['collectionId'] === \spl_object_id($collection)) { |
216 | $contents = '<p title="Seconds"><strong>Time to Serve:</strong> ' |
217 | . \round($data['end'] - $data['start'], 6) |
218 | . '</p>'; |
219 | break; |
220 | } |
221 | } |
222 | return $contents; |
223 | } |
224 | |
225 | protected function renderRouteCollectionsTable(RouteCollection $collection) : string |
226 | { |
227 | $routesCount = \count($collection); |
228 | \ob_start(); |
229 | echo '<p><strong>Routes Count:</strong> ' . $routesCount . '</p>'; |
230 | echo $this->renderRouteCollectionTime($collection); |
231 | if ($routesCount === 0) { |
232 | echo '<p>No route has been set in this collection.</p>'; |
233 | return \ob_get_clean(); // @phpstan-ignore-line |
234 | } |
235 | // @phpstan-ignore-next-line |
236 | if ($routesCount === 1 && $collection->router->getMatchedOrigin() && $collection->getRouteNotFound()) { |
237 | echo '<p>Only Route Not Found has been set in this collection.</p>'; |
238 | return \ob_get_clean(); // @phpstan-ignore-line |
239 | } ?> |
240 | <table> |
241 | <thead> |
242 | <tr> |
243 | <th>#</th> |
244 | <th>Method</th> |
245 | <th>Path</th> |
246 | <th>Action</th> |
247 | <th>Name</th> |
248 | <th>Has Options</th> |
249 | </tr> |
250 | </thead> |
251 | <tbody> |
252 | <?php foreach ($this->getRoutes($collection) as $index => $route): ?> |
253 | <tr<?= $route['matched'] ? ' class="active" title="Matched Route"' : '' ?>> |
254 | <td><?= ++$index ?></td> |
255 | <td><?= \htmlentities($route['method']) ?></td> |
256 | <td><?= $this->toCodeBrackets(\htmlentities($route['path'])) ?></td> |
257 | <td><?= \htmlentities($route['action']) ?></td> |
258 | <td><?= \htmlentities((string) $route['name']) ?></td> |
259 | <td><?= \htmlentities($route['hasOptions']) ?></td> |
260 | </tr> |
261 | <?php endforeach ?> |
262 | </tbody> |
263 | </table> |
264 | <?php |
265 | return \ob_get_clean(); // @phpstan-ignore-line |
266 | } |
267 | |
268 | /** |
269 | * @param RouteCollection $collection |
270 | * |
271 | * @return array<array<string,mixed>> |
272 | */ |
273 | protected function getRoutes(RouteCollection $collection) : array |
274 | { |
275 | $result = []; |
276 | $collectionRoutes = $collection->routes; |
277 | \ksort($collectionRoutes); |
278 | foreach ($collectionRoutes as $method => $routes) { |
279 | foreach ($routes as $route) { |
280 | $result[] = [ |
281 | 'method' => $method, |
282 | 'path' => $route->getPath(), |
283 | 'action' => \is_string($route->getAction()) ? $route->getAction() : 'Closure', |
284 | 'name' => $route->getName(), |
285 | 'hasOptions' => $route->getOptions() ? 'Yes' : 'No', |
286 | 'matched' => $route === $this->router->getMatchedRoute(), |
287 | ]; |
288 | } |
289 | } |
290 | return $result; |
291 | } |
292 | |
293 | protected function toCodeBrackets(string $str) : string |
294 | { |
295 | return \strtr($str, [ |
296 | '{' => '<code>{', |
297 | '}' => '}</code>', |
298 | ]); |
299 | } |
300 | } |