Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [Auto Routing Improved] Module Routing #7416

Merged
merged 6 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/Config/Routing.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
}
17 changes: 17 additions & 0 deletions system/Commands/Utilities/Routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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'],
Expand All @@ -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']);
Expand Down
10 changes: 10 additions & 0 deletions system/Router/AutoRouterImproved.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Router\Exceptions\MethodNotFoundException;
use Config\Routing;
use ReflectionClass;
use ReflectionException;

Expand Down Expand Up @@ -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);

Expand Down
26 changes: 24 additions & 2 deletions tests/system/Router/AutoRouterImprovedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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');
Expand Down
2 changes: 2 additions & 0 deletions user_guide_src/source/changelogs/v4.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <creating-a-layout>` 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.
Expand Down
26 changes: 26 additions & 0 deletions user_guide_src/source/incoming/routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down