Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
192 / 192
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
RoutingCollector
100.00% covered (success)
100.00%
192 / 192
100.00% covered (success)
100.00%
9 / 9
49
100.00% covered (success)
100.00%
1 / 1
 setRouter
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getActivities
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
5
 getContents
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
8
 renderMatchedRoute
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
1 / 1
10
 renderRouteCollections
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
8
 renderRouteCollectionTime
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 renderRouteCollectionsTable
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
7
 getRoutes
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
 toCodeBrackets
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
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 */
10namespace Framework\Routing\Debug;
11
12use Closure;
13use Framework\Debug\Collector;
14use Framework\Routing\RouteCollection;
15use Framework\Routing\Router;
16
17/**
18 * Class RoutingCollector.
19 *
20 * @package routing
21 */
22class 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}