Skip to content

Commit a645ccc

Browse files
feature #59909 [FrameworkBundle] Add --method option to debug:router command (santysisi)
This PR was merged into the 7.3 branch. Discussion ---------- [FrameworkBundle] Add `--method` option to `debug:router` command | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Feature #59906 | License | MIT ### Description I have added a new `InputOption` named `method` to the `debug:router` command. This option allows developers to filter the displayed routes by HTTP method (GET, POST, PUT, DELETE, etc.) when running the command. This improvement makes it easier and more efficient to debug routes, especially in large applications. It also aligns the `debug:router` command with the `router:match` command, which already includes a similar option for filtering by HTTP method. ### Changes Made * Added the --method option to the debug:router command. * Routes can now be filtered based on the HTTP method used (GET, POST, PUT, DELETE, etc.). * This enhances debugging capabilities and streamlines the process of working with specific route methods. ### Example Before adding the new InputOption: `php bin/console debug:router` ![Image](https://github.com/user-attachments/assets/c8e4fe51-0ba0-46dc-8369-2bc156873b2d) After adding the new InputOption: `php bin/console debug:router --method=GET` ![Image](https://github.com/user-attachments/assets/5edcee2f-8761-4af9-919a-a741d6024e51) Before adding the new InputOption with the name argument `php bin/console debug:router app_foo` ![Image](https://github.com/user-attachments/assets/16470489-acba-49c2-8fe0-fd856702d052) After adding the method InputOption with the name argument: php bin/console debug:router app_foo --method=DELETE ![Image](https://github.com/user-attachments/assets/419b739f-794b-4427-9c8d-64babaaa4245) Commits ------- 6a98d493e40 Add 'method' option to debug:router command to filter routes by HTTP method
2 parents 320d587 + cfe5c8b commit a645ccc

13 files changed

+248
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CHANGELOG
1313
* Add `framework.validation.disable_translation` option
1414
* Add support for signal plain name in the `messenger.stop_worker_on_signals` configuration
1515
* Deprecate the `framework.validation.cache` option
16+
* Add `--method` option to the `debug:router` command
1617

1718
7.2
1819
---

Command/RouterDebugCommand.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ protected function configure(): void
5555
new InputOption('show-aliases', null, InputOption::VALUE_NONE, 'Show aliases in overview'),
5656
new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'),
5757
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'),
58+
new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Filter by HTTP method', '', ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
5859
])
5960
->setHelp(<<<'EOF'
6061
The <info>%command.name%</info> displays the configured routes:
@@ -76,6 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7677
{
7778
$io = new SymfonyStyle($input, $output);
7879
$name = $input->getArgument('name');
80+
$method = strtoupper($input->getOption('method'));
7981
$helper = new DescriptorHelper($this->fileLinkFormatter);
8082
$routes = $this->router->getRouteCollection();
8183
$container = null;
@@ -85,7 +87,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8587

8688
if ($name) {
8789
$route = $routes->get($name);
88-
$matchingRoutes = $this->findRouteNameContaining($name, $routes);
90+
$matchingRoutes = $this->findRouteNameContaining($name, $routes, $method);
8991

9092
if (!$input->isInteractive() && !$route && \count($matchingRoutes) > 1) {
9193
$helper->describe($io, $this->findRouteContaining($name, $routes), [
@@ -94,6 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9496
'show_controllers' => $input->getOption('show-controllers'),
9597
'show_aliases' => $input->getOption('show-aliases'),
9698
'output' => $io,
99+
'method' => $method,
97100
]);
98101

99102
return 0;
@@ -124,17 +127,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
124127
'show_aliases' => $input->getOption('show-aliases'),
125128
'output' => $io,
126129
'container' => $container,
130+
'method' => $method,
127131
]);
128132
}
129133

130134
return 0;
131135
}
132136

133-
private function findRouteNameContaining(string $name, RouteCollection $routes): array
137+
private function findRouteNameContaining(string $name, RouteCollection $routes, string $method): array
134138
{
135139
$foundRoutesNames = [];
136140
foreach ($routes as $routeName => $route) {
137-
if (false !== stripos($routeName, $name)) {
141+
if (false !== stripos($routeName, $name) && (!$method || !$route->getMethods() || \in_array($method, $route->getMethods(), true))) {
138142
$foundRoutesNames[] = $routeName;
139143
}
140144
}

Console/Descriptor/Descriptor.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function describe(OutputInterface $output, mixed $object, array $options
4949
}
5050

5151
match (true) {
52-
$object instanceof RouteCollection => $this->describeRouteCollection($object, $options),
52+
$object instanceof RouteCollection => $this->describeRouteCollection($this->filterRoutesByHttpMethod($object, $options['method'] ?? ''), $options),
5353
$object instanceof Route => $this->describeRoute($object, $options),
5454
$object instanceof ParameterBag => $this->describeContainerParameters($object, $options),
5555
$object instanceof ContainerBuilder && !empty($options['env-vars']) => $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options),
@@ -360,4 +360,20 @@ protected function getServiceEdges(ContainerBuilder $container, string $serviceI
360360
return [];
361361
}
362362
}
363+
364+
private function filterRoutesByHttpMethod(RouteCollection $routes, string $method): RouteCollection
365+
{
366+
if (!$method) {
367+
return $routes;
368+
}
369+
$filteredRoutes = clone $routes;
370+
371+
foreach ($filteredRoutes as $routeName => $route) {
372+
if ($route->getMethods() && !\in_array($method, $route->getMethods(), true)) {
373+
$filteredRoutes->remove($routeName);
374+
}
375+
}
376+
377+
return $filteredRoutes;
378+
}
363379
}

Tests/Console/Descriptor/AbstractDescriptorTestCase.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ public static function getDescribeRouteCollectionTestData(): array
5050
return static::getDescriptionTestData(ObjectsProvider::getRouteCollections());
5151
}
5252

53+
/** @dataProvider getDescribeRouteCollectionWithHttpMethodFilterTestData */
54+
public function testDescribeRouteCollectionWithHttpMethodFilter(string $httpMethod, RouteCollection $routes, $expectedDescription)
55+
{
56+
$this->assertDescription($expectedDescription, $routes, ['method' => $httpMethod]);
57+
}
58+
59+
public static function getDescribeRouteCollectionWithHttpMethodFilterTestData(): iterable
60+
{
61+
foreach (ObjectsProvider::getRouteCollectionsByHttpMethod() as $httpMethod => $routeCollection) {
62+
foreach (static::getDescriptionTestData($routeCollection) as $testData) {
63+
yield [$httpMethod, ...$testData];
64+
}
65+
}
66+
}
67+
5368
/** @dataProvider getDescribeRouteTestData */
5469
public function testDescribeRoute(Route $route, $expectedDescription)
5570
{
@@ -273,6 +288,7 @@ private function assertDescription($expectedDescription, $describedObject, array
273288
$options['is_debug'] = false;
274289
$options['raw_output'] = true;
275290
$options['raw_text'] = true;
291+
$options['method'] ??= null;
276292
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
277293

278294
if ('txt' === $this->getFormat()) {

Tests/Console/Descriptor/ObjectsProvider.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,38 @@ public static function getRouteCollections()
3737
return ['route_collection_1' => $collection1];
3838
}
3939

40+
public static function getRouteCollectionsByHttpMethod(): array
41+
{
42+
$collection = new RouteCollection();
43+
foreach (self::getRoutes() as $name => $route) {
44+
$collection->add($name, $route);
45+
}
46+
47+
// Clone the original collection and add a route without any specific method restrictions
48+
$collectionWithRouteWithoutMethodRestriction = clone $collection;
49+
$collectionWithRouteWithoutMethodRestriction->add(
50+
'route_3',
51+
new RouteStub(
52+
'/other/route',
53+
[],
54+
[],
55+
['opt1' => 'val1', 'opt2' => 'val2'],
56+
'localhost',
57+
['http', 'https'],
58+
[],
59+
)
60+
);
61+
62+
return [
63+
'GET' => [
64+
'route_collection_2' => $collectionWithRouteWithoutMethodRestriction,
65+
],
66+
'PUT' => [
67+
'route_collection_3' => $collection,
68+
],
69+
];
70+
}
71+
4072
public static function getRoutes()
4173
{
4274
return [
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"route_1": {
3+
"path": "\/hello\/{name}",
4+
"pathRegex": "#PATH_REGEX#",
5+
"host": "localhost",
6+
"hostRegex": "#HOST_REGEX#",
7+
"scheme": "http|https",
8+
"method": "GET|HEAD",
9+
"class": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\RouteStub",
10+
"defaults": {
11+
"name": "Joseph"
12+
},
13+
"requirements": {
14+
"name": "[a-z]+"
15+
},
16+
"options": {
17+
"compiler_class": "Symfony\\Component\\Routing\\RouteCompiler",
18+
"opt1": "val1",
19+
"opt2": "val2"
20+
}
21+
},
22+
"route_3": {
23+
"path": "\/other\/route",
24+
"pathRegex": "#PATH_REGEX#",
25+
"host": "localhost",
26+
"hostRegex": "#HOST_REGEX#",
27+
"scheme": "http|https",
28+
"method": "ANY",
29+
"class": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\RouteStub",
30+
"defaults": [],
31+
"requirements": "NO CUSTOM",
32+
"options": {
33+
"compiler_class": "Symfony\\Component\\Routing\\RouteCompiler",
34+
"opt1": "val1",
35+
"opt2": "val2"
36+
}
37+
}
38+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
route_1
2+
-------
3+
4+
- Path: /hello/{name}
5+
- Path Regex: #PATH_REGEX#
6+
- Host: localhost
7+
- Host Regex: #HOST_REGEX#
8+
- Scheme: http|https
9+
- Method: GET|HEAD
10+
- Class: Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub
11+
- Defaults:
12+
- `name`: Joseph
13+
- Requirements:
14+
- `name`: [a-z]+
15+
- Options:
16+
- `compiler_class`: Symfony\Component\Routing\RouteCompiler
17+
- `opt1`: val1
18+
- `opt2`: val2
19+
20+
21+
route_3
22+
-------
23+
24+
- Path: /other/route
25+
- Path Regex: #PATH_REGEX#
26+
- Host: localhost
27+
- Host Regex: #HOST_REGEX#
28+
- Scheme: http|https
29+
- Method: ANY
30+
- Class: Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub
31+
- Defaults: NONE
32+
- Requirements: NO CUSTOM
33+
- Options:
34+
- `compiler_class`: Symfony\Component\Routing\RouteCompiler
35+
- `opt1`: val1
36+
- `opt2`: val2
37+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
--------- ---------- ------------ ----------- ---------------
2+
 Name   Method   Scheme   Host   Path 
3+
--------- ---------- ------------ ----------- ---------------
4+
route_1 GET|HEAD http|https localhost /hello/{name}
5+
route_3 ANY http|https localhost /other/route
6+
--------- ---------- ------------ ----------- ---------------
7+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<routes>
3+
<route name="route_1" class="Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub">
4+
<path regex="#PATH_REGEX#">/hello/{name}</path>
5+
<host regex="#HOST_REGEX#">localhost</host>
6+
<scheme>http</scheme>
7+
<scheme>https</scheme>
8+
<method>GET</method>
9+
<method>HEAD</method>
10+
<defaults>
11+
<default key="name">Joseph</default>
12+
</defaults>
13+
<requirements>
14+
<requirement key="name">[a-z]+</requirement>
15+
</requirements>
16+
<options>
17+
<option key="compiler_class">Symfony\Component\Routing\RouteCompiler</option>
18+
<option key="opt1">val1</option>
19+
<option key="opt2">val2</option>
20+
</options>
21+
</route>
22+
<route name="route_3" class="Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub">
23+
<path regex="#PATH_REGEX#">/other/route</path>
24+
<host regex="#HOST_REGEX#">localhost</host>
25+
<scheme>http</scheme>
26+
<scheme>https</scheme>
27+
<options>
28+
<option key="compiler_class">Symfony\Component\Routing\RouteCompiler</option>
29+
<option key="opt1">val1</option>
30+
<option key="opt2">val2</option>
31+
</options>
32+
</route>
33+
</routes>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"route_2": {
3+
"path": "\/name\/add",
4+
"pathRegex": "#PATH_REGEX#",
5+
"host": "localhost",
6+
"hostRegex": "#HOST_REGEX#",
7+
"scheme": "http|https",
8+
"method": "PUT|POST",
9+
"class": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\RouteStub",
10+
"defaults": [],
11+
"requirements": "NO CUSTOM",
12+
"options": {
13+
"compiler_class": "Symfony\\Component\\Routing\\RouteCompiler",
14+
"opt1": "val1",
15+
"opt2": "val2"
16+
},
17+
"condition": "context.getMethod() in ['GET', 'HEAD', 'POST']"
18+
}
19+
}

0 commit comments

Comments
 (0)