Skip to content

Commit cfe5c8b

Browse files
santysisinicolas-grekas
authored andcommitted
Add 'method' option to debug:router command to filter routes by HTTP method
1 parent e2828af commit cfe5c8b

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)