From d19a39306256d7598a0e9cec2095413fb275cbdd Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 11:16:46 +0900 Subject: [PATCH 1/8] feat: add DefinedRouteCollector --- system/Router/DefinedRouteCollector.php | 63 +++++++++++++ .../Router/DefinedRouteCollectorTest.php | 90 +++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 system/Router/DefinedRouteCollector.php create mode 100644 tests/system/Router/DefinedRouteCollectorTest.php diff --git a/system/Router/DefinedRouteCollector.php b/system/Router/DefinedRouteCollector.php new file mode 100644 index 000000000000..f9e0454b7b07 --- /dev/null +++ b/system/Router/DefinedRouteCollector.php @@ -0,0 +1,63 @@ + + * + * 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; + +class DefinedRouteCollector +{ + private RouteCollection $routeCollection; + + public function __construct(RouteCollection $routes) + { + $this->routeCollection = $routes; + } + + 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..61647967766c --- /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 test() + { + $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); + } +} From d1224ccd97760e94463ec9288da50b98f207823b Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 11:31:17 +0900 Subject: [PATCH 2/8] refactor: use DefinedRouteCollector in `spark routes` --- system/Commands/Utilities/Routes.php | 35 ++++++++++++---------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php index 97b990bb1092..68d2f8d07d50 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['route']; - $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'])), + ]; } } From 06faf737beb08996b234719ba92c6ecd4c2e8b79 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 11:51:32 +0900 Subject: [PATCH 3/8] refactor: use DefinedRouteCollector in DebugBar --- system/Debug/Toolbar/Collectors/Routes.php | 42 +++++++--------------- 1 file changed, 13 insertions(+), 29 deletions(-) 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'], + ]; } } From 6f0d950c33a9a7d2b4c1d98fd6b4313098ee56aa Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 12:03:19 +0900 Subject: [PATCH 4/8] refactor: use DefinedRouteCollector in `spark routes` --- system/Commands/Utilities/Routes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php index 68d2f8d07d50..ee726d4aa93d 100644 --- a/system/Commands/Utilities/Routes.php +++ b/system/Commands/Utilities/Routes.php @@ -123,7 +123,7 @@ public function run(array $params) $sampleUri = $uriGenerator->get($route['route']); $filters = $filterCollector->get($route['method'], $sampleUri); - $routeName = ($route['route'] === $route['name']) ? '»' : $route['route']; + $routeName = ($route['route'] === $route['name']) ? '»' : $route['name']; $tbody[] = [ strtoupper($route['method']), From 453511c582b2ee3829be115f4b83abcc29441e49 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 12:08:14 +0900 Subject: [PATCH 5/8] docs: add comment --- system/Router/DefinedRouteCollector.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/Router/DefinedRouteCollector.php b/system/Router/DefinedRouteCollector.php index f9e0454b7b07..c1d98a5ea95b 100644 --- a/system/Router/DefinedRouteCollector.php +++ b/system/Router/DefinedRouteCollector.php @@ -14,6 +14,9 @@ use Closure; use Generator; +/** + * Collect all defined routes for display. + */ class DefinedRouteCollector { private RouteCollection $routeCollection; From ea62e0b022dccda589aef1333b6bf36166c6ba0c Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 18:30:52 +0900 Subject: [PATCH 6/8] fix: add final keyword to DefinedRouteCollector --- system/Router/DefinedRouteCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Router/DefinedRouteCollector.php b/system/Router/DefinedRouteCollector.php index c1d98a5ea95b..62aebce51ba0 100644 --- a/system/Router/DefinedRouteCollector.php +++ b/system/Router/DefinedRouteCollector.php @@ -17,7 +17,7 @@ /** * Collect all defined routes for display. */ -class DefinedRouteCollector +final class DefinedRouteCollector { private RouteCollection $routeCollection; From f6e636256ea157c4818e6f93fbacb8c256a65e4b Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 18:31:58 +0900 Subject: [PATCH 7/8] test: fix test method name --- tests/system/Router/DefinedRouteCollectorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Router/DefinedRouteCollectorTest.php b/tests/system/Router/DefinedRouteCollectorTest.php index 61647967766c..619f4386886f 100644 --- a/tests/system/Router/DefinedRouteCollectorTest.php +++ b/tests/system/Router/DefinedRouteCollectorTest.php @@ -43,7 +43,7 @@ private function createRouteCollection(array $config = [], $moduleConfig = null) return (new RouteCollection($loader, $moduleConfig, new Routing()))->setHTTPVerb('get'); } - public function test() + public function testCollect() { $routes = $this->createRouteCollection(); $routes->get('journals', 'Blogs'); From f5a284154c78d68cbe85e9ad76f82e0f85e2b3d3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 4 Jul 2023 18:48:26 +0900 Subject: [PATCH 8/8] docs: add @phpstan-return --- system/Router/DefinedRouteCollector.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/Router/DefinedRouteCollector.php b/system/Router/DefinedRouteCollector.php index 62aebce51ba0..eeae28379d04 100644 --- a/system/Router/DefinedRouteCollector.php +++ b/system/Router/DefinedRouteCollector.php @@ -26,6 +26,9 @@ public function __construct(RouteCollection $routes) $this->routeCollection = $routes; } + /** + * @phpstan-return Generator + */ public function collect(): Generator { $methods = [