diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 98966a5a18bb2..7223e42d28f4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -18,6 +18,7 @@ CHANGELOG * Made `BrowserKitAssertionsTrait` report the original error message in case of a failure * Added ability for `config:dump-reference` and `debug:config` to dump and debug kernel container extension configuration. * Deprecated `session.attribute_bag` service and `session.flash_bag` service. + * Added `debug:router --sort` option to see routes sorted by name or path as well as by priority 5.0.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 16acf7a7db9c4..b1fc862d0d2e3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -20,6 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouterInterface; @@ -58,6 +59,7 @@ protected function configure() new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), + new InputOption('sort', null, InputOption::VALUE_REQUIRED, 'The sorting criterion (priority, name, or path)', 'priority'), ]) ->setDescription('Displays current routes for an application') ->setHelp(<<<'EOF' @@ -80,8 +82,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $name = $input->getArgument('name'); $helper = new DescriptorHelper($this->fileLinkFormatter); - $routes = $this->router->getRouteCollection(); $container = $this->fileLinkFormatter ? \Closure::fromCallable([$this, 'getContainerBuilder']) : null; + $sortOption = $input->getOption('sort'); + $routes = $this->sortRoutes($this->router->getRouteCollection(), $sortOption); + if ('priority' !== $sortOption) { + $io->caution(sprintf('The routes are not sorted in the order they get matched but by %s.', $sortOption)); + } if ($name) { if (!($route = $routes->get($name)) && $matchingRoutes = $this->findRouteNameContaining($name, $routes)) { @@ -125,4 +131,22 @@ private function findRouteNameContaining(string $name, RouteCollection $routes): return $foundRoutesNames; } + + private function sortRoutes(RouteCollection $routes, string $propertyName): RouteCollection + { + $sortedRoutes = $routes->all(); + if ('name' === $propertyName) { + ksort($sortedRoutes); + } elseif ('path' === $propertyName) { + uasort($sortedRoutes, function ($a, $b) { return $a->getPath() <=> $b->getPath(); }); + } elseif ('priority' !== $propertyName) { + throw new InvalidArgumentException(sprintf('The option "%s" is not valid.', $propertyName)); + } + $routeCollection = new RouteCollection(); + foreach ($sortedRoutes as $routeName => $route) { + $routeCollection->add($routeName, $route); + } + + return $routeCollection; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php new file mode 100644 index 0000000000000..02159882d7460 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php @@ -0,0 +1,186 @@ +createCommandTester(); + $result = $tester->execute(['--sort' => 'priority'], ['decorated' => false]); + $this->assertSame(0, $result, 'Returns 0 in case of success'); + $this->assertRegExp('/(charlie).*\n\W*(alfa).*\n\W*(delta).*\n\W*(bravo)/m', $tester->getDisplay(true)); + $this->assertStringNotContainsString('! [CAUTION] The routes list is not sorted in the parsing order.', $tester->getDisplay(true)); + } + + public function testSortingByName() + { + $tester = $this->createCommandTester(); + $result = $tester->execute(['--sort' => 'name'], ['decorated' => false]); + $this->assertSame(0, $result, 'Returns 0 in case of success'); + $this->assertRegExp('/(alfa).*\n\W*(bravo).*\n\W*(charlie).*\n\W*(delta)/m', $tester->getDisplay(true)); + $this->assertStringContainsString('! [CAUTION] The routes list is not sorted in the parsing order.', $tester->getDisplay(true)); + } + + public function testSortingByPath() + { + $tester = $this->createCommandTester(); + $result = $tester->execute(['--sort' => 'path'], ['decorated' => false]); + $this->assertSame(0, $result, 'Returns 0 in case of success'); + $this->assertRegExp('/(\/romeo).*\n.*(\/sierra).*\n.*(\/tango).*\n.*(\/uniform)/m', $tester->getDisplay(true)); + $this->assertStringContainsString('! [CAUTION] The routes list is not sorted in the parsing order.', $tester->getDisplay(true)); + } + + public function testThrowsExceptionWithInvalidParameter() + { + $tester = $this->createCommandTester(); + $this->expectExceptionMessage('The option "foobar" is not valid'); + $tester->execute(['--sort' => 'foobar'], ['decorated' => false]); + } + + public function testThrowsExceptionWithNullParameter() + { + $tester = $this->createCommandTester(); + $this->expectExceptionMessage('The "--sort" option requires a value.'); + $tester->execute(['--sort' => null], ['decorated' => false]); + } + + public function testSortingByPriorityWithDuplicatePath() + { + $tester = $this->createCommandTesterWithDuplicatePath(); + $result = $tester->execute(['--sort' => 'priority'], ['decorated' => false]); + $this->assertSame(0, $result, 'Returns 0 in case of success'); + $this->assertRegExp('/(charlie).*\n\W*(alfa).*\n\W*(delta).*\n\W*(bravo).*\n\W*(echo)/m', $tester->getDisplay(true)); + $this->assertStringNotContainsString('! [CAUTION] The routes list is not sorted in the parsing order.', $tester->getDisplay(true)); + } + + public function testSortingByNameWithDuplicatePath() + { + $tester = $this->createCommandTesterWithDuplicatePath(); + $result = $tester->execute(['--sort' => 'name'], ['decorated' => false]); + $this->assertSame(0, $result, 'Returns 0 in case of success'); + $this->assertRegExp('/(alfa).*\n\W*(bravo).*\n\W*(charlie).*\n\W*(delta).*\n\W*(echo)/m', $tester->getDisplay(true)); + $this->assertStringContainsString('! [CAUTION] The routes list is not sorted in the parsing order.', $tester->getDisplay(true)); + } + + public function testSortingByPathWithDuplicatePath() + { + $tester = $this->createCommandTesterWithDuplicatePath(); + $result = $tester->execute(['--sort' => 'path'], ['decorated' => false]); + $this->assertSame(0, $result, 'Returns 0 in case of success'); + $this->assertRegExp('/(\/romeo).*\n.*(\/sierra).*\n.*(\/tango).*\n.*(\/uniform).*\n.*(\/uniform)/m', $tester->getDisplay(true)); + $this->assertStringContainsString('! [CAUTION] The routes list is not sorted in the parsing order.', $tester->getDisplay(true)); + } + + public function testWithoutCallingSortOptionExplicitly() + { + $tester = $this->createCommandTester(); + $result = $tester->execute([], ['decorated' => false]); + $this->assertSame(0, $result, 'Returns 0 in case of success'); + $this->assertRegExp('/(charlie).*\n\W*(alfa).*\n\W*(delta).*\n\W*(bravo)/m', $tester->getDisplay(true)); + $this->assertStringNotContainsString('! [CAUTION] The routes list is not sorted in the parsing order.', $tester->getDisplay(true)); + } + + private function createCommandTester(): CommandTester + { + $application = new Application($this->getKernel()); + $application->add(new RouterDebugCommand($this->getRouter())); + + return new CommandTester($application->find('debug:router')); + } + + private function createCommandTesterWithDuplicatePath(): CommandTester + { + $application = new Application($this->getKernel()); + $application->add(new RouterDebugCommand($this->getRouterWithDuplicatePath())); + + return new CommandTester($application->find('debug:router')); + } + + private function getRouter() + { + $routeCollection = new RouteCollection(); + $routeCollection->add('charlie', new Route('uniform')); + $routeCollection->add('alfa', new Route('sierra')); + $routeCollection->add('delta', new Route('tango')); + $routeCollection->add('bravo', new Route('romeo')); + $requestContext = new RequestContext(); + $router = $this->createMock(RouterInterface::class); + $router + ->expects($this->any()) + ->method('getRouteCollection') + ->willReturn($routeCollection); + $router + ->expects($this->any()) + ->method('getContext') + ->willReturn($requestContext); + + return $router; + } + + private function getRouterWithDuplicatePath() + { + $routeCollection = new RouteCollection(); + $routeCollection->add('charlie', (new Route('uniform'))->setMethods('GET')); + $routeCollection->add('alfa', new Route('sierra')); + $routeCollection->add('delta', new Route('tango')); + $routeCollection->add('bravo', new Route('romeo')); + $routeCollection->add('echo', (new Route('uniform'))->setMethods('POST')); + + $requestContext = new RequestContext(); + $router = $this->createMock(RouterInterface::class); + $router + ->expects($this->any()) + ->method('getRouteCollection') + ->willReturn($routeCollection); + $router + ->expects($this->any()) + ->method('getContext') + ->willReturn($requestContext); + + return $router; + } + + private function getKernel() + { + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('has') + ->willReturnCallback(function ($id) { + return 'console.command_loader' !== $id; + }) + ; + $container + ->expects($this->any()) + ->method('get') + ->with('router') + ->willReturn($this->getRouter()) + ; + + $kernel = $this->createMock(KernelInterface::class); + $kernel + ->expects($this->any()) + ->method('getContainer') + ->willReturn($container) + ; + $kernel + ->expects($this->once()) + ->method('getBundles') + ->willReturn([]) + ; + + return $kernel; + } +}
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: