diff --git a/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReader.php b/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReader.php index 3f373c433f1b..aaa0986df345 100644 --- a/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReader.php +++ b/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReader.php @@ -76,18 +76,37 @@ public function read(string $class, string $defaultController = 'Home', string $ ); if ($routeWithoutController !== []) { + // Route for the default controller. $output = [...$output, ...$routeWithoutController]; continue; } + $params = []; + $routeParams = ''; + $refParams = $method->getParameters(); + + foreach ($refParams as $param) { + $required = true; + if ($param->isOptional()) { + $required = false; + + $routeParams .= '[/..]'; + } else { + $routeParams .= '/..'; + } + + // [variable_name => required?] + $params[$param->getName()] = $required; + } + // Route for the default method. $output[] = [ 'method' => $httpVerb, 'route' => $classInUri, - 'route_params' => '', + 'route_params' => $routeParams, 'handler' => '\\' . $classname . '::' . $methodName, - 'params' => [], + 'params' => $params, ]; continue; diff --git a/system/Router/AutoRouterImproved.php b/system/Router/AutoRouterImproved.php index 39b264ecf780..87409e78f107 100644 --- a/system/Router/AutoRouterImproved.php +++ b/system/Router/AutoRouterImproved.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Router; use CodeIgniter\Exceptions\PageNotFoundException; +use CodeIgniter\Router\Exceptions\MethodNotFoundException; use ReflectionClass; use ReflectionException; @@ -168,17 +169,30 @@ public function getRoute(string $uri): array '\\' ); - // Ensure routes registered via $routes->cli() are not accessible via web. + // Ensure the controller is not defined in routes. $this->protectDefinedRoutes(); - // Check _remap() + // Ensure the controller does not have _remap() method. $this->checkRemap(); - // Check parameters + // Check parameter count try { $this->checkParameters($uri); - } catch (ReflectionException $e) { - throw PageNotFoundException::forControllerNotFound($this->controller, $this->method); + } catch (MethodNotFoundException $e) { + // Fallback to the default method + if (! isset($methodSegment)) { + throw PageNotFoundException::forControllerNotFound($this->controller, $this->method); + } + + array_unshift($this->params, $methodSegment); + $method = $this->method; + $this->method = $this->defaultMethod; + + try { + $this->checkParameters($uri); + } catch (MethodNotFoundException $e) { + throw PageNotFoundException::forControllerNotFound($this->controller, $method); + } } return [$this->directory, $this->controller, $this->method, $this->params]; @@ -201,12 +215,21 @@ private function protectDefinedRoutes(): void private function checkParameters(string $uri): void { - $refClass = new ReflectionClass($this->controller); - $refMethod = $refClass->getMethod($this->method); - $refParams = $refMethod->getParameters(); + try { + $refClass = new ReflectionClass($this->controller); + } catch (ReflectionException $e) { + throw PageNotFoundException::forControllerNotFound($this->controller, $this->method); + } + + try { + $refMethod = $refClass->getMethod($this->method); + $refParams = $refMethod->getParameters(); + } catch (ReflectionException $e) { + throw new MethodNotFoundException(); + } if (! $refMethod->isPublic()) { - throw PageNotFoundException::forMethodNotFound($this->method); + throw new MethodNotFoundException(); } if (count($refParams) < count($this->params)) { diff --git a/system/Router/Exceptions/MethodNotFoundException.php b/system/Router/Exceptions/MethodNotFoundException.php new file mode 100644 index 000000000000..d9ad45a5fa3d --- /dev/null +++ b/system/Router/Exceptions/MethodNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Router\Exceptions; + +use RuntimeException; + +/** + * @internal + */ +final class MethodNotFoundException extends RuntimeException +{ +} diff --git a/tests/_support/Controllers/Newautorouting.php b/tests/_support/Controllers/Newautorouting.php index f3c8479f3685..2c31547ed2de 100644 --- a/tests/_support/Controllers/Newautorouting.php +++ b/tests/_support/Controllers/Newautorouting.php @@ -15,7 +15,7 @@ class Newautorouting extends Controller { - public function getIndex() + public function getIndex(string $m = '') { return 'Hello'; } diff --git a/tests/system/Commands/RoutesTest.php b/tests/system/Commands/RoutesTest.php index 6d506274145d..8567dc6bc70a 100644 --- a/tests/system/Commands/RoutesTest.php +++ b/tests/system/Commands/RoutesTest.php @@ -129,7 +129,7 @@ public function testRoutesCommandAutoRouteImproved() | TRACE | testing | testing-index | \App\Controllers\TestController::index | | toolbar | | CONNECT | testing | testing-index | \App\Controllers\TestController::index | | toolbar | | CLI | testing | testing-index | \App\Controllers\TestController::index | | | - | GET(auto) | newautorouting | | \Tests\Support\Controllers\Newautorouting::getIndex | | toolbar | + | GET(auto) | newautorouting[/..] | | \Tests\Support\Controllers\Newautorouting::getIndex | | toolbar | | POST(auto) | newautorouting/save/../..[/..] | | \Tests\Support\Controllers\Newautorouting::postSave | | toolbar | +------------+--------------------------------+---------------+-----------------------------------------------------+----------------+---------------+ EOL; diff --git a/tests/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollectorTest.php b/tests/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollectorTest.php index 4b99de7dbb07..72ebc1cf336a 100644 --- a/tests/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollectorTest.php +++ b/tests/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollectorTest.php @@ -62,7 +62,7 @@ public function testGetFilterMatches() $expected = [ 0 => [ 'GET(auto)', - 'newautorouting', + 'newautorouting[/..]', '', '\\Tests\\Support\\Controllers\\Newautorouting::getIndex', '', @@ -90,7 +90,7 @@ public function testGetFilterDoesNotMatch() $expected = [ 0 => [ 'GET(auto)', - 'newautorouting', + 'newautorouting[/..]', '', '\\Tests\\Support\\Controllers\\Newautorouting::getIndex', '', diff --git a/tests/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReaderTest.php b/tests/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReaderTest.php index 6a77f0173ab5..6dd81f4722f2 100644 --- a/tests/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReaderTest.php +++ b/tests/system/Commands/Utilities/Routes/AutoRouterImproved/ControllerMethodReaderTest.php @@ -43,9 +43,11 @@ public function testRead() 0 => [ 'method' => 'get', 'route' => 'newautorouting', - 'route_params' => '', + 'route_params' => '[/..]', 'handler' => '\Tests\Support\Controllers\Newautorouting::getIndex', - 'params' => [], + 'params' => [ + 'm' => false, + ], ], [ 'method' => 'post', diff --git a/tests/system/Router/AutoRouterImprovedTest.php b/tests/system/Router/AutoRouterImprovedTest.php index ec94676be121..ab1fa7db2188 100644 --- a/tests/system/Router/AutoRouterImprovedTest.php +++ b/tests/system/Router/AutoRouterImprovedTest.php @@ -199,6 +199,19 @@ public function testAutoRouteFindsDefaultDashFolder() $this->assertSame([], $params); } + public function testAutoRouteFallbackToDefaultMethod() + { + $router = $this->createNewAutoRouter(); + + [$directory, $controller, $method, $params] + = $router->getRoute('index/15'); + + $this->assertNull($directory); + $this->assertSame('\\' . Index::class, $controller); + $this->assertSame('getIndex', $method); + $this->assertSame(['15'], $params); + } + public function testAutoRouteRejectsSingleDot() { $this->expectException(PageNotFoundException::class); diff --git a/tests/system/Router/Controllers/Index.php b/tests/system/Router/Controllers/Index.php index bfc3539e0b1f..2ad043942923 100644 --- a/tests/system/Router/Controllers/Index.php +++ b/tests/system/Router/Controllers/Index.php @@ -15,7 +15,7 @@ class Index extends Controller { - public function getIndex() + public function getIndex($p1 = '') { } diff --git a/user_guide_src/source/changelogs/v4.4.0.rst b/user_guide_src/source/changelogs/v4.4.0.rst index 6632a061c4d1..748829333165 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -58,8 +58,11 @@ Helpers and Functions Others ====== -- **View:** Added optional 2nd parameter ``$saveData`` on ``renderSection()`` to prevent from auto cleans the data after displaying. See :ref:`View Layouts ` 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 use URI without a method name like + ``product/15`` where ``15`` is an arbitrary number. + See :ref:`controller-default-method-fallback` for details. Message Changes *************** diff --git a/user_guide_src/source/incoming/controllers.rst b/user_guide_src/source/incoming/controllers.rst index cd7023653b55..fda8d74cd1f5 100644 --- a/user_guide_src/source/incoming/controllers.rst +++ b/user_guide_src/source/incoming/controllers.rst @@ -263,6 +263,29 @@ Your method will be passed URI segments 3 and 4 (``'sandals'`` and ``'123'``): .. literalinclude:: controllers/022.php +.. _controller-default-method-fallback: + +Default Method Fallback +======================= + +.. versionadded:: 4.4.0 + +If the controller method corresponding to the URI segment of the method name +does not exist, and if the default method is defined, the URI segments are +passed to the default method for execution. + +.. literalinclude:: controllers/024.php + +Load the following URL:: + + example.com/index.php/product/15/edit + +The method will be passed URI segments 2 and 3 (``'15'`` and ``'edit'``): + +.. note:: If there are more parameters in the URI than the method parameters, + Auto Routing (Improved) does not execute the method, and it results in 404 + Not Found. + Defining a Default Controller ============================= diff --git a/user_guide_src/source/incoming/controllers/024.php b/user_guide_src/source/incoming/controllers/024.php new file mode 100644 index 000000000000..c3ae1a7c6a43 --- /dev/null +++ b/user_guide_src/source/incoming/controllers/024.php @@ -0,0 +1,11 @@ +