Issues (1)

src/Command/RouterTreeCommand.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Bone\Router\Command;
6
7
use Bone\Http\RouterInterface;
8
use Bone\Router\Router;
9
use League\Route\Route;
10
use League\Route\RouteGroup;
11
use League\Route\Router as LeagueRouter;
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use Psr\Http\Server\RequestHandlerInterface;
15
use ReflectionClass;
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Console\Style\SymfonyStyle;
21
use function array_keys;
22
use function array_shift;
23
use function count;
24
use function explode;
25
use function trim;
26
use function usort;
27
28
class RouterTreeCommand extends Command
29
{
30
    private bool $spacing = false;
31
32
    public function __construct(private readonly Router $router)
33
    {
34
        parent::__construct('router:tree');
35
    }
36
37
    public function configure(): void
38
    {
39
        $this->setDescription('Display all routes in a tree display');
40
        $this->setHelp('List all routes in a hierarchical tree');
41
        $this->addOption('space', 's', InputOption::VALUE_NONE,'display with more space');
42
    }
43
44
    public function execute(InputInterface $input, OutputInterface $output): int
45
    {
46
        $this->spacing = $input->getOption('space') ? true : false;
47
        $io = new SymfonyStyle($input, $output);
48
        $io->title('Configured routes :');
49
        $groups = $this->router->getGroups();
50
51
        foreach ($groups as $group) {
52
            $this->getRoutes($group);
53
        }
54
55
        $routes = $this->router->getRoutes();
56
        $sort = function (Route $a, Route $b) {
57
            return $a->getPath() <=> $b->getPath();
58
        };
59
        usort($routes, $sort);
60
        $paths = [];
61
62
        foreach ($routes as $route) {
63
            $paths[$route->getMethod()][] = $route->getPath();
64
        }
65
66
        foreach ($paths as $method => $methodPaths) {
67
68
            $io->info($method . ' paths:');
69
            $tree = $this->buildTree($methodPaths);
70
            $method === 'GET' ? array_shift($tree) : null;
71
            $output->writeln('/');
72
            $this->printTree($tree, $output, '');
73
            $io->writeln('');
74
        }
75
76
        return Command::SUCCESS;
77
    }
78
79
    private function buildTree(array $urls): array
80
    {
81
        $tree = [];
82
        foreach ($urls as $url) {
83
            $parts = explode('/', trim($url, '/')); // Split the URL into parts
84
            $current = &$tree; // Start at the root of the tree
85
86
            foreach ($parts as $part) {
87
                if (!isset($current[$part])) {
88
                    $current[$part] = [];
89
                }
90
                $current = &$current[$part]; // Move down the tree
91
            }
92
        }
93
94
        return $tree;
95
    }
96
97
    private function printTree(array $tree, OutputInterface $output, string $indent = '') : void
98
    {
99
        $keys = array_keys($tree);
100
        $totalKeys = count($keys);
101
102
        foreach ($keys as $index => $key) {
103
            if ($index === 0  && $this->spacing === true) {
104
                $output->writeln($indent . '|');
105
            }
106
107
            $output->write($indent . '|_ ' . $key);
108
109
            // Check if this node has only one child, in which case we collapse it
110
            $useChild = false;
111
112
            if (count($tree[$key]) == 1) {
113
                // Find the only child node and print it directly without further recursion
114
                $child = array_keys($tree[$key])[0];
115
                $output->write( '/' . $child);
116
                $useChild = true;
117
            }
118
119
            $output->write("\n");
120
            $node = $useChild ? $tree[$key][$child] : $tree[$key];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $child does not seem to be defined for all execution paths leading up to this point.
Loading history...
121
122
            // Recursively print the subtree, adjusting the indentation
123
            if (!empty($node) && count($node) > 1) {
124
                $newIndent = $index + 1 === $totalKeys ? $indent . '   ' : $indent . '|  ';
125
                $this->printTree($node, $output, $newIndent);
126
            }
127
128
            // If there's another sibling node, print '|'
129
            if ($index < $totalKeys - 1 && $this->spacing === true) {
130
                $output->write($indent . '| ' . "\n");  // Adjusts the visual output with proper spacing
131
            }
132
        }
133
    }
134
135
    private function getRoutes(RouteGroup $group): void
136
    {
137
        $mirror = new ReflectionClass(RouteGroup::class);
138
        $callback = $mirror->getProperty('callback')->getValue($group);
139
        $callback($group);
140
    }
141
}
142