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 | } |