diff --git a/app/Config/Routing.php b/app/Config/Routing.php index 73d9a93f0f4b..e3183d2e8db8 100644 --- a/app/Config/Routing.php +++ b/app/Config/Routing.php @@ -97,4 +97,17 @@ class Routing extends BaseRouting * Default: false */ public bool $prioritize = false; + + /** + * Map of URI segments and namespaces. For Auto Routing (Improved). + * + * The key is the first URI segment. The value is the controller namespace. + * E.g., + * [ + * 'blog' => 'Acme\Blog\Controllers', + * ] + * + * @var array [ uri_segment => namespace ] + */ + public array $moduleRoutes = []; } diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php index 3b0112db828a..83ca40a7c1bd 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 Config\Routing; use Config\Services; /** @@ -152,6 +153,22 @@ public function run(array $params) ); $autoRoutes = $autoRouteCollector->get(); + + // Check for Module Routes. + if ($routingConfig = config(Routing::class)) { + foreach ($routingConfig->moduleRoutes as $uri => $namespace) { + $autoRouteCollector = new AutoRouteCollectorImproved( + $namespace, + $collection->getDefaultController(), + $collection->getDefaultMethod(), + $methods, + $collection->getRegisteredControllers('*'), + $uri + ); + + $autoRoutes = [...$autoRoutes, ...$autoRouteCollector->get()]; + } + } } else { $autoRouteCollector = new AutoRouteCollector( $collection->getDefaultNamespace(), diff --git a/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php b/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php index f2de8e4b4a6d..2b6096016f7e 100644 --- a/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php +++ b/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php @@ -35,6 +35,11 @@ final class AutoRouteCollector */ private array $protectedControllers; + /** + * @var string URI prefix for Module Routing + */ + private string $prefix; + /** * @param string $namespace namespace to search */ @@ -43,13 +48,15 @@ public function __construct( string $defaultController, string $defaultMethod, array $httpMethods, - array $protectedControllers + array $protectedControllers, + string $prefix = '' ) { $this->namespace = $namespace; $this->defaultController = $defaultController; $this->defaultMethod = $defaultMethod; $this->httpMethods = $httpMethods; $this->protectedControllers = $protectedControllers; + $this->prefix = $prefix; } /** @@ -82,9 +89,18 @@ public function get(): array $routes = $this->addFilters($routes); foreach ($routes as $item) { + $route = $item['route'] . $item['route_params']; + + // For module routing + if ($this->prefix !== '' && $route === '/') { + $route = $this->prefix; + } elseif ($this->prefix !== '') { + $route = $this->prefix . '/' . $route; + } + $tbody[] = [ strtoupper($item['method']) . '(auto)', - $item['route'] . $item['route_params'], + $route, '', $item['handler'], $item['before'], @@ -101,13 +117,22 @@ private function addFilters($routes) $filterCollector = new FilterCollector(true); foreach ($routes as &$route) { + $routePath = $route['route']; + + // For module routing + if ($this->prefix !== '' && $route === '/') { + $routePath = $this->prefix; + } elseif ($this->prefix !== '') { + $routePath = $this->prefix . '/' . $routePath; + } + // Search filters for the URI with all params $sampleUri = $this->generateSampleUri($route); - $filtersLongest = $filterCollector->get($route['method'], $route['route'] . $sampleUri); + $filtersLongest = $filterCollector->get($route['method'], $routePath . $sampleUri); // Search filters for the URI without optional params $sampleUri = $this->generateSampleUri($route, false); - $filtersShortest = $filterCollector->get($route['method'], $route['route'] . $sampleUri); + $filtersShortest = $filterCollector->get($route['method'], $routePath . $sampleUri); // Get common array elements $filters['before'] = array_intersect($filtersLongest['before'], $filtersShortest['before']); diff --git a/system/Router/AutoRouterImproved.php b/system/Router/AutoRouterImproved.php index 87409e78f107..000afda126bd 100644 --- a/system/Router/AutoRouterImproved.php +++ b/system/Router/AutoRouterImproved.php @@ -13,6 +13,7 @@ use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Router\Exceptions\MethodNotFoundException; +use Config\Routing; use ReflectionClass; use ReflectionException; @@ -112,6 +113,15 @@ public function getRoute(string $uri): array { $segments = explode('/', $uri); + // Check for Module Routes. + if ( + ($routingConfig = config(Routing::class)) + && array_key_exists($segments[0], $routingConfig->moduleRoutes) + ) { + $uriSegment = array_shift($segments); + $this->namespace = rtrim($routingConfig->moduleRoutes[$uriSegment], '\\') . '\\'; + } + // WARNING: Directories get shifted out of the segments array. $nonDirSegments = $this->scanControllers($segments); diff --git a/tests/system/Router/AutoRouterImprovedTest.php b/tests/system/Router/AutoRouterImprovedTest.php index d6560e1eb9e6..75a5f49b8648 100644 --- a/tests/system/Router/AutoRouterImprovedTest.php +++ b/tests/system/Router/AutoRouterImprovedTest.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Router; +use CodeIgniter\Config\Factories; use CodeIgniter\Config\Services; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Router\Controllers\Dash_folder\Dash_controller; @@ -39,11 +40,11 @@ protected function setUp(): void $this->collection = new RouteCollection(Services::locator(), $moduleConfig, new Routing()); } - private function createNewAutoRouter(string $httpVerb = 'get'): AutoRouterImproved + private function createNewAutoRouter(string $httpVerb = 'get', $namespace = 'CodeIgniter\Router\Controllers'): AutoRouterImproved { return new AutoRouterImproved( [], - 'CodeIgniter\Router\Controllers', + $namespace, $this->collection->getDefaultController(), $this->collection->getDefaultMethod(), true, @@ -66,6 +67,27 @@ public function testAutoRouteFindsDefaultControllerAndMethodGet() $this->assertSame([], $params); } + public function testAutoRouteFindsModuleDefaultControllerAndMethodGet() + { + $config = config(Routing::class); + $config->moduleRoutes = [ + 'test' => 'CodeIgniter\Router\Controllers', + ]; + Factories::injectMock('config', Routing::class, $config); + + $this->collection->setDefaultController('Index'); + + $router = $this->createNewAutoRouter('get', 'App/Controllers'); + + [$directory, $controller, $method, $params] + = $router->getRoute('test'); + + $this->assertNull($directory); + $this->assertSame('\\' . Index::class, $controller); + $this->assertSame('getIndex', $method); + $this->assertSame([], $params); + } + public function testAutoRouteFindsDefaultControllerAndMethodPost() { $this->collection->setDefaultController('Index'); diff --git a/user_guide_src/source/changelogs/v4.4.0.rst b/user_guide_src/source/changelogs/v4.4.0.rst index 3c4fe991d3c6..29a8b030f9ba 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -88,6 +88,8 @@ Others the ``Content-Disposition: inline`` header to display the file in the browser. See :ref:`open-file-in-browser` for details. - **View:** Added optional 2nd parameter ``$saveData`` on ``renderSection()`` to prevent from auto cleans the data after displaying. See :ref:`View Layouts ` for details. +- **Auto Routing (Improved)**: Now you can route to Modules. + See :ref:`auto-routing-improved-module-routing` for details. - **Auto Routing (Improved)**: Now you can use URI without a method name like ``product/15`` where ``15`` is an arbitrary number. See :ref:`controller-default-method-fallback` for details. diff --git a/user_guide_src/source/incoming/routing.rst b/user_guide_src/source/incoming/routing.rst index 066009beda65..b2bbde1aea66 100644 --- a/user_guide_src/source/incoming/routing.rst +++ b/user_guide_src/source/incoming/routing.rst @@ -716,6 +716,32 @@ In this example, if the user were to visit **example.com/products**, and a ``Pro .. note:: You cannot access the controller with the URI of the default method name. In the example above, you can access **example.com/products**, but if you access **example.com/products/listall**, it will be not found. +.. _auto-routing-improved-module-routing: + +Module Routing +============== + +.. versionadded:: 4.4.0 + +You can use auto routing even if you use :doc:`../general/modules` and place +the controllers in a different namespace. + +To route to a module, the ``$moduleRoutes`` property in **app/Config/Routing.php** +must be set:: + + public array $moduleRoutes = [ + 'blog' => 'Acme\Blog\Controllers', + ]; + +The key is the first URI segment for the module, and the value is the controller +namespace. In the above configuration, **http://localhost:8080/blog/foo/bar** +will be routed to ``Acme\Blog\Controllers\Foo::getBar()``. + +.. note:: If you define ``$moduleRoutes``, the routing for the module takes + precedence. In the above example, even if you have the ``App\Controllers\Blog`` + controller, **http://localhost:8080/blog** will be routed to the default + controller ``Acme\Blog\Controllers\Home``. + .. _auto-routing-legacy: Auto Routing (Legacy)