diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php index 97b990bb1092..ee726d4aa93d 100644 --- a/system/Commands/Utilities/Routes.php +++ b/system/Commands/Utilities/Routes.php @@ -18,6 +18,7 @@ use CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved\AutoRouteCollector as AutoRouteCollectorImproved; use CodeIgniter\Commands\Utilities\Routes\FilterCollector; use CodeIgniter\Commands\Utilities\Routes\SampleURIGenerator; +use CodeIgniter\Router\DefinedRouteCollector; use Config\Feature; use Config\Routing; use Config\Services; @@ -115,29 +116,23 @@ public function run(array $params) $uriGenerator = new SampleURIGenerator(); $filterCollector = new FilterCollector(); - foreach ($methods as $method) { - $routes = $collection->getRoutes($method); + $definedRouteCollector = new DefinedRouteCollector($collection); - foreach ($routes as $route => $handler) { - if (is_string($handler) || $handler instanceof Closure) { - $sampleUri = $uriGenerator->get($route); - $filters = $filterCollector->get($method, $sampleUri); + foreach ($definedRouteCollector->collect() as $route) { + if (is_string($route['handler']) || $route['handler'] instanceof Closure) { + $sampleUri = $uriGenerator->get($route['route']); + $filters = $filterCollector->get($route['method'], $sampleUri); - if ($handler instanceof Closure) { - $handler = '(Closure)'; - } - - $routeName = $collection->getRoutesOptions($route)['as'] ?? '»'; + $routeName = ($route['route'] === $route['name']) ? '»' : $route['name']; - $tbody[] = [ - strtoupper($method), - $route, - $routeName, - $handler, - implode(' ', array_map('class_basename', $filters['before'])), - implode(' ', array_map('class_basename', $filters['after'])), - ]; - } + $tbody[] = [ + strtoupper($route['method']), + $route['route'], + $routeName, + $route['handler'], + implode(' ', array_map('class_basename', $filters['before'])), + implode(' ', array_map('class_basename', $filters['after'])), + ]; } } diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php index 5ea88c0411c8..0420c19b92dd 100644 --- a/system/Debug/Toolbar/Collectors/Routes.php +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; +use CodeIgniter\Router\DefinedRouteCollector; use Config\Services; use ReflectionException; use ReflectionFunction; @@ -55,9 +56,6 @@ public function display(): array $rawRoutes = Services::routes(true); $router = Services::router(null, null, true); - // Matched Route - $route = $router->getMatchedRoute(); - // Get our parameters // Closure routes if (is_callable($router->controllerName())) { @@ -100,32 +98,18 @@ public function display(): array ]; // Defined Routes - $routes = []; - $methods = [ - 'get', - 'head', - 'post', - 'patch', - 'put', - 'delete', - 'options', - 'trace', - 'connect', - 'cli', - ]; - - foreach ($methods as $method) { - $raw = $rawRoutes->getRoutes($method); - - foreach ($raw as $route => $handler) { - // filter for strings, as callbacks aren't displayable - if (is_string($handler)) { - $routes[] = [ - 'method' => strtoupper($method), - 'route' => $route, - 'handler' => $handler, - ]; - } + $routes = []; + + $definedRouteCollector = new DefinedRouteCollector($rawRoutes); + + foreach ($definedRouteCollector->collect() as $route) { + // filter for strings, as callbacks aren't displayable + if ($route['handler'] !== '(Closure)') { + $routes[] = [ + 'method' => strtoupper($route['method']), + 'route' => $route['route'], + 'handler' => $route['handler'], + ]; } } diff --git a/system/Router/DefinedRouteCollector.php b/system/Router/DefinedRouteCollector.php new file mode 100644 index 000000000000..eeae28379d04 --- /dev/null +++ b/system/Router/DefinedRouteCollector.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Router; + +use Closure; +use Generator; + +/** + * Collect all defined routes for display. + */ +final class DefinedRouteCollector +{ + private RouteCollection $routeCollection; + + public function __construct(RouteCollection $routes) + { + $this->routeCollection = $routes; + } + + /** + * @phpstan-return Generator + */ + public function collect(): Generator + { + $methods = [ + 'get', + 'head', + 'post', + 'patch', + 'put', + 'delete', + 'options', + 'trace', + 'connect', + 'cli', + ]; + + foreach ($methods as $method) { + $routes = $this->routeCollection->getRoutes($method); + + foreach ($routes as $route => $handler) { + if (is_string($handler) || $handler instanceof Closure) { + + if ($handler instanceof Closure) { + $handler = '(Closure)'; + } + + $routeName = $this->routeCollection->getRoutesOptions($route)['as'] ?? $route; + + yield [ + 'method' => $method, + 'route' => $route, + 'name' => $routeName, + 'handler' => $handler, + ]; + } + } + } + } +} diff --git a/tests/system/Router/DefinedRouteCollectorTest.php b/tests/system/Router/DefinedRouteCollectorTest.php new file mode 100644 index 000000000000..619f4386886f --- /dev/null +++ b/tests/system/Router/DefinedRouteCollectorTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Router; + +use CodeIgniter\Config\Services; +use CodeIgniter\Test\CIUnitTestCase; +use Config\Modules; +use Config\Routing; + +/** + * @internal + * + * @group Others + */ +final class DefinedRouteCollectorTest extends CIUnitTestCase +{ + private function createRouteCollection(array $config = [], $moduleConfig = null): RouteCollection + { + $defaults = [ + 'Config' => APPPATH . 'Config', + 'App' => APPPATH, + ]; + $config = array_merge($config, $defaults); + + Services::autoloader()->addNamespace($config); + + $loader = Services::locator(); + + if ($moduleConfig === null) { + $moduleConfig = new Modules(); + $moduleConfig->enabled = false; + } + + return (new RouteCollection($loader, $moduleConfig, new Routing()))->setHTTPVerb('get'); + } + + public function testCollect() + { + $routes = $this->createRouteCollection(); + $routes->get('journals', 'Blogs'); + $routes->get('product/(:num)', 'Catalog::productLookupByID/$1'); + $routes->get('feed', static fn () => 'A Closure route.'); + $routes->view('about', 'pages/about'); + + $collector = new DefinedRouteCollector($routes); + + $definedRoutes = []; + + foreach ($collector->collect() as $route) { + $definedRoutes[] = $route; + } + + $expected = [ + [ + 'method' => 'get', + 'route' => 'journals', + 'name' => 'journals', + 'handler' => '\App\Controllers\Blogs', + ], + [ + 'method' => 'get', + 'route' => 'product/([0-9]+)', + 'name' => 'product/([0-9]+)', + 'handler' => '\App\Controllers\Catalog::productLookupByID/$1', + ], + [ + 'method' => 'get', + 'route' => 'feed', + 'name' => 'feed', + 'handler' => '(Closure)', + ], + [ + 'method' => 'get', + 'route' => 'about', + 'name' => 'about', + 'handler' => '(Closure)', + ], + ]; + $this->assertSame($expected, $definedRoutes); + } +}