Passed
Push — master ( c82314...7a7802 )
by Filipe
15:08 queued 14s
created

RouterDebugMatchCommand::prepareContext()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 13
rs 10
cc 4
nc 8
nop 1
1
<?php
2
3
/**
4
 * This file is part of web-stack
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Slick\WebStack\Infrastructure\Console;
13
14
use Symfony\Component\Console\Attribute\AsCommand;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Input\ArrayInput;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use Symfony\Component\Console\Style\SymfonyStyle;
22
use Symfony\Component\Routing\Matcher\TraceableUrlMatcher;
23
use Symfony\Component\Routing\RequestContext;
24
use Symfony\Component\Routing\RouterInterface;
25
26
/**
27
 * RouterDebugMatchCommand
28
 *
29
 * @package Slick\WebStack\Infrastructure\Console
30
 */
31
#[AsCommand(name: 'router:match', description: 'Help debug routes by simulating a path info match')]
32
final class RouterDebugMatchCommand extends Command
33
{
34
    public function __construct(private RouterInterface $router)
35
    {
36
        parent::__construct();
37
    }
38
39
    protected function configure(): void
40
    {
41
        $this
42
            ->setDefinition([
43
                new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'),
44
                new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Set the HTTP method'),
45
                new InputOption(
46
                    'scheme',
47
                    null,
48
                    InputOption::VALUE_REQUIRED,
49
                    'Set the URI scheme (usually http or https)'
50
                ),
51
                new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'),
52
            ])
53
            ->setHelp(<<<'EOF'
54
The <info>router:match</info> shows which routes match a given request and which don't and for what reason:
55
56
  <info>php router:match /foo</info>
57
58
or
59
60
  <info>php router:match/foo --method POST --scheme https --host symfony.com --verbose</info>
61
62
EOF
63
            )
64
        ;
65
    }
66
67
    protected function execute(InputInterface $input, OutputInterface $output): int
68
    {
69
        $style = new SymfonyStyle($input, $output);
70
71
        $matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $this->prepareContext($input));
72
        $traces = $matcher->getTraces($input->getArgument('path_info'));
73
74
        $style->newLine();
75
76
        $matches = false;
77
        foreach ($traces as $trace) {
78
            if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) {
79
                $style->text(
80
                    sprintf('Route <info>"%s"</> almost matches but %s', $trace['name'], lcfirst($trace['log']))
81
                );
82
            } elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) {
83
                $style->success(sprintf('Route "%s" matches', $trace['name']));
84
85
                $routerDebugCommand = $this->getApplication()?->find('debug:router');
86
                if (null !== $routerDebugCommand) {
87
                    $routerDebugCommand->run(new ArrayInput(['name' => $trace['name']]), $output);
88
                }
89
90
                $matches = true;
91
            } elseif ($input->getOption('verbose')) {
92
                $style->text(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log']));
93
            }
94
        }
95
96
        if (!$matches) {
97
            $style->error(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info')));
98
99
            return Command::FAILURE;
100
        }
101
102
        return Command::SUCCESS;
103
    }
104
105
    /**
106
     * @param InputInterface $input
107
     * @return RequestContext
108
     */
109
    private function prepareContext(InputInterface $input): RequestContext
110
    {
111
        $context = $this->router->getContext();
112
        if (null !== $method = $input->getOption('method')) {
113
            $context->setMethod($method);
114
        }
115
        if (null !== $scheme = $input->getOption('scheme')) {
116
            $context->setScheme($scheme);
117
        }
118
        if (null !== $host = $input->getOption('host')) {
119
            $context->setHost($host);
120
        }
121
        return $context;
122
    }
123
}
124