From fbfc4bbab4851a8f35b2416ed98075ea590e835b Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 8 Apr 2022 10:10:11 +0900 Subject: [PATCH 01/18] docs: update PHPDocs --- system/CodeIgniter.php | 2 ++ system/Router/RouteCollectionInterface.php | 4 ++-- system/Router/Router.php | 6 ++---- system/Router/RouterInterface.php | 21 +++++++++------------ 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 093687254c77..3bc71d632993 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -792,6 +792,8 @@ protected function tryToRouteIt(?RouteCollectionInterface $routes = null) /** * Determines the path to use for us to try to route to, based * on user input (setPath), or the CLI/IncomingRequest path. + * + * @return string */ protected function determinePath() { diff --git a/system/Router/RouteCollectionInterface.php b/system/Router/RouteCollectionInterface.php index a55b5d80a0b9..e5e4350a5db6 100644 --- a/system/Router/RouteCollectionInterface.php +++ b/system/Router/RouteCollectionInterface.php @@ -131,7 +131,7 @@ public function getDefaultMethod(); /** * Returns the current value of the translateURIDashes setting. * - * @return mixed + * @return bool */ public function shouldTranslateURIDashes(); @@ -145,7 +145,7 @@ public function shouldAutoRoute(); /** * Returns the raw array of available routes. * - * @return mixed + * @return array */ public function getRoutes(); diff --git a/system/Router/Router.php b/system/Router/Router.php index 37c88114172e..477aa5db130d 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -115,8 +115,6 @@ class Router implements RouterInterface /** * Stores a reference to the RouteCollection object. - * - * @param Request $request */ public function __construct(RouteCollectionInterface $routes, ?Request $request = null) { @@ -132,7 +130,7 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request * @throws PageNotFoundException * @throws RedirectException * - * @return mixed|string + * @return Closure|string Controller classname or Closure */ public function handle(?string $uri = null) { @@ -204,7 +202,7 @@ public function getFilters(): array /** * Returns the name of the matched controller. * - * @return Closure|string + * @return Closure|string Controller classname or Closure */ public function controllerName() { diff --git a/system/Router/RouterInterface.php b/system/Router/RouterInterface.php index e252b56decc8..1efeb2e2c6e2 100644 --- a/system/Router/RouterInterface.php +++ b/system/Router/RouterInterface.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Router; +use Closure; use CodeIgniter\HTTP\Request; /** @@ -20,33 +21,29 @@ interface RouterInterface { /** * Stores a reference to the RouteCollection object. - * - * @param Request $request */ public function __construct(RouteCollectionInterface $routes, ?Request $request = null); /** - * Scans the URI and attempts to match the current URI to the - * one of the defined routes in the RouteCollection. + * Finds the controller method corresponding to the URI. * * @param string $uri * - * @return mixed + * @return Closure|string Controller classname or Closure */ public function handle(?string $uri = null); /** * Returns the name of the matched controller. * - * @return mixed + * @return Closure|string Controller classname or Closure */ public function controllerName(); /** - * Returns the name of the method to run in the - * chosen container. + * Returns the name of the method in the controller to run. * - * @return mixed + * @return string */ public function methodName(); @@ -55,19 +52,19 @@ public function methodName(); * during the parsing process as an array, ready to send to * instance->method(...$params). * - * @return mixed + * @return array */ public function params(); /** * Sets the value that should be used to match the index.php file. Defaults - * to index.php but this allows you to modify it in case your are using + * to index.php but this allows you to modify it in case you are using * something like mod_rewrite to remove the page. This allows you to set * it a blank. * * @param string $page * - * @return mixed + * @return RouterInterface */ public function setIndexPage($page); } From 1004321c8ef0c8221c19f50161abb06b37fa34dd Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 8 Apr 2022 10:42:35 +0900 Subject: [PATCH 02/18] docs: add comments --- system/Router/Router.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/Router/Router.php b/system/Router/Router.php index 477aa5db130d..3d1cff12fcc3 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -120,6 +120,7 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request { $this->collection = $routes; + // These are only for auto-routing $this->controller = $this->collection->getDefaultController(); $this->method = $this->collection->getDefaultMethod(); @@ -137,7 +138,7 @@ public function handle(?string $uri = null) $this->translateURIDashes = $this->collection->shouldTranslateURIDashes(); // If we cannot find a URI to match against, then - // everything runs off of it's default settings. + // everything runs off of its default settings. if ($uri === null || $uri === '') { return strpos($this->controller, '\\') === false ? $this->collection->getDefaultNamespace() . $this->controller @@ -151,6 +152,7 @@ public function handle(?string $uri = null) $this->filterInfo = null; $this->filtersInfo = []; + // Checks defined routes if ($this->checkRoutes($uri)) { if ($this->collection->isFiltered($this->matchedRoute[0])) { $multipleFiltersEnabled = config('Feature')->multipleFilters ?? false; @@ -172,6 +174,7 @@ public function handle(?string $uri = null) throw new PageNotFoundException("Can't find a route for '{$uri}'."); } + // Checks auto routes $this->autoRoute($uri); return $this->controllerName(); @@ -336,6 +339,8 @@ public function getLocale() } /** + * Checks Defined Routs. + * * Compares the uri string against the routes that the * RouteCollection class defined for us, attempting to find a match. * This method will modify $this->controller, etal as needed. From 56781cbcfd0e8768958654489fe5c4ac75a2ae34 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 8 Apr 2022 10:50:12 +0900 Subject: [PATCH 03/18] refactor: move property initialization to constructor --- system/Router/Router.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Router/Router.php b/system/Router/Router.php index 3d1cff12fcc3..80e0d24fb9af 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -125,6 +125,8 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request $this->method = $this->collection->getDefaultMethod(); $this->collection->setHTTPVerb($request->getMethod() ?? strtolower($_SERVER['REQUEST_METHOD'])); + + $this->translateURIDashes = $this->collection->shouldTranslateURIDashes(); } /** @@ -135,8 +137,6 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request */ public function handle(?string $uri = null) { - $this->translateURIDashes = $this->collection->shouldTranslateURIDashes(); - // If we cannot find a URI to match against, then // everything runs off of its default settings. if ($uri === null || $uri === '') { From 0b505c62565a21d030e7f9efe0b7998528ecd2d7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 8 Apr 2022 13:50:07 +0900 Subject: [PATCH 04/18] refactor: extract AutoRouter class --- phpstan-baseline.neon.dist | 7 +- system/Router/AutoRouter.php | 277 +++++++++++++++++++++++++++++++++++ system/Router/Router.php | 113 ++++---------- 3 files changed, 312 insertions(+), 85 deletions(-) create mode 100644 system/Router/AutoRouter.php diff --git a/phpstan-baseline.neon.dist b/phpstan-baseline.neon.dist index 53d3710b7ea7..6193a69d7c1c 100644 --- a/phpstan-baseline.neon.dist +++ b/phpstan-baseline.neon.dist @@ -722,9 +722,14 @@ parameters: - message: "#^Method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getRoutes\\(\\) invoked with 1 parameter, 0 required\\.$#" - count: 2 + count: 1 path: system/Router/Router.php + - + message: "#^Method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getRoutes\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: system/Router/AutoRouter.php + - message: "#^Property Config\\\\App\\:\\:\\$CSRF[a-zA-Z]+ \\([a-zA-Z]+\\) on left side of \\?\\? is not nullable\\.$#" count: 6 diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php new file mode 100644 index 000000000000..3d43ffb28209 --- /dev/null +++ b/system/Router/AutoRouter.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Router; + +use CodeIgniter\Exceptions\PageNotFoundException; + +/** + * Router for Auto-Routing + */ +class AutoRouter +{ + /** + * A RouteCollection instance. + */ + protected RouteCollectionInterface $collection; + + /** + * Sub-directory that contains the requested controller class. + * Primarily used by 'autoRoute'. + */ + protected ?string $directory = null; + + /** + * The name of the controller class. + */ + protected string $controller; + + /** + * The name of the method to use. + */ + protected string $method; + + /** + * An array of params to the controller method. + */ + protected array $params = []; + + /** + * Whether dashes in URI's should be converted + * to underscores when determining method names. + */ + protected bool $translateURIDashes; + + /** + * HTTP verb for the request. + */ + protected string $httpVerb; + + /** + * Default namespace for controllers. + */ + protected string $defaultNamespace; + + public function __construct( + RouteCollectionInterface $routes, + string $defaultNamespace, + bool $translateURIDashes, + string $httpVerb + ) { + $this->collection = $routes; + $this->defaultNamespace = $defaultNamespace; + $this->translateURIDashes = $translateURIDashes; + $this->httpVerb = $httpVerb; + + $this->controller = $this->collection->getDefaultController(); + $this->method = $this->collection->getDefaultMethod(); + } + + /** + * Attempts to match a URI path against Controllers and directories + * found in APPPATH/Controllers, to find a matching route. + * + * @return array [directory_name, controller_name, controller_method, params] + */ + public function getRoute(string $uri): array + { + $segments = explode('/', $uri); + + // WARNING: Directories get shifted out of the segments array. + $segments = $this->scanControllers($segments); + + // If we don't have any segments left - use the default controller; + // If not empty, then the first segment should be the controller + if (! empty($segments)) { + $this->controller = ucfirst(array_shift($segments)); + } + + $controllerName = $this->controllerName(); + + if (! $this->isValidSegment($controllerName)) { + throw new PageNotFoundException($this->controller . ' is not a valid controller name'); + } + + // Use the method name if it exists. + // If it doesn't, no biggie - the default method name + // has already been set. + if (! empty($segments)) { + $this->method = array_shift($segments) ?: $this->method; + } + + // Prevent access to initController method + if (strtolower($this->method) === 'initcontroller') { + throw PageNotFoundException::forPageNotFound(); + } + + if (! empty($segments)) { + $this->params = $segments; + } + + // Ensure routes registered via $routes->cli() are not accessible via web. + if ($this->httpVerb !== 'cli') { + $controller = '\\' . $this->defaultNamespace; + + $controller .= $this->directory ? str_replace('/', '\\', $this->directory) : ''; + $controller .= $controllerName; + + $controller = strtolower($controller); + $methodName = strtolower($this->methodName()); + + foreach ($this->collection->getRoutes('cli') as $route) { + if (is_string($route)) { + $route = strtolower($route); + if (strpos($route, $controller . '::' . $methodName) === 0) { + throw new PageNotFoundException(); + } + + if ($route === $controller) { + throw new PageNotFoundException(); + } + } + } + } + + // Load the file so that it's available for CodeIgniter. + $file = APPPATH . 'Controllers/' . $this->directory . $controllerName . '.php'; + if (is_file($file)) { + include_once $file; + } + + // Ensure the controller stores the fully-qualified class name + // We have to check for a length over 1, since by default it will be '\' + if (strpos($this->controller, '\\') === false && strlen($this->defaultNamespace) > 1) { + $this->controller = '\\' . ltrim(str_replace('/', '\\', $this->defaultNamespace . $this->directory . $controllerName), '\\'); + } + + return [$this->directory, $this->controllerName(), $this->methodName(), $this->params]; + } + + /** + * Tells the system whether we should translate URI dashes or not + * in the URI from a dash to an underscore. + */ + public function setTranslateURIDashes(bool $val = false): self + { + $this->translateURIDashes = $val; + + return $this; + } + + /** + * Scans the controller directory, attempting to locate a controller matching the supplied uri $segments + * + * @param array $segments URI segments + * + * @return array returns an array of remaining uri segments that don't map onto a directory + */ + protected function scanControllers(array $segments): array + { + $segments = array_filter($segments, static fn ($segment) => $segment !== ''); + // numerically reindex the array, removing gaps + $segments = array_values($segments); + + // if a prior directory value has been set, just return segments and get out of here + if (isset($this->directory)) { + return $segments; + } + + // Loop through our segments and return as soon as a controller + // is found or when such a directory doesn't exist + $c = count($segments); + + while ($c-- > 0) { + $segmentConvert = ucfirst($this->translateURIDashes === true ? str_replace('-', '_', $segments[0]) : $segments[0]); + // as soon as we encounter any segment that is not PSR-4 compliant, stop searching + if (! $this->isValidSegment($segmentConvert)) { + return $segments; + } + + $test = APPPATH . 'Controllers/' . $this->directory . $segmentConvert; + + // as long as each segment is *not* a controller file but does match a directory, add it to $this->directory + if (! is_file($test . '.php') && is_dir($test)) { + $this->setDirectory($segmentConvert, true, false); + array_shift($segments); + + continue; + } + + return $segments; + } + + // This means that all segments were actually directories + return $segments; + } + + /** + * Returns true if the supplied $segment string represents a valid PSR-4 compliant namespace/directory segment + * + * regex comes from https://www.php.net/manual/en/language.variables.basics.php + */ + private function isValidSegment(string $segment): bool + { + return (bool) preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $segment); + } + + /** + * Sets the sub-directory that the controller is in. + * + * @param bool $validate if true, checks to make sure $dir consists of only PSR4 compliant segments + */ + public function setDirectory(?string $dir = null, bool $append = false, bool $validate = true) + { + if (empty($dir)) { + $this->directory = null; + + return; + } + + if ($validate) { + $segments = explode('/', trim($dir, '/')); + + foreach ($segments as $segment) { + if (! $this->isValidSegment($segment)) { + return; + } + } + } + + if ($append !== true || empty($this->directory)) { + $this->directory = trim($dir, '/') . '/'; + } else { + $this->directory .= trim($dir, '/') . '/'; + } + } + + /** + * Returns the name of the sub-directory the controller is in, + * if any. Relative to APPPATH.'Controllers'. + */ + public function directory(): string + { + return ! empty($this->directory) ? $this->directory : ''; + } + + private function controllerName(): string + { + return $this->translateURIDashes + ? str_replace('-', '_', $this->controller) + : $this->controller; + } + + private function methodName(): string + { + return $this->translateURIDashes + ? str_replace('-', '_', $this->method) + : $this->method; + } +} diff --git a/system/Router/Router.php b/system/Router/Router.php index 80e0d24fb9af..673d95bc1d9b 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -113,6 +113,11 @@ class Router implements RouterInterface */ protected $filtersInfo = []; + /** + * @var AutoRouter|null + */ + protected $autoRouter; + /** * Stores a reference to the RouteCollection object. */ @@ -127,6 +132,13 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request $this->collection->setHTTPVerb($request->getMethod() ?? strtolower($_SERVER['REQUEST_METHOD'])); $this->translateURIDashes = $this->collection->shouldTranslateURIDashes(); + + $this->autoRouter = new AutoRouter( + $this->collection, + $this->collection->getDefaultNamespace(), + $this->translateURIDashes, + $this->collection->getHTTPVerb() + ); } /** @@ -264,10 +276,12 @@ public function params(): array * if any. Relative to APPPATH.'Controllers'. * * Only used when auto-routing is turned on. + * + * @deprecated Moved to AutoRouter class. */ public function directory(): string { - return ! empty($this->directory) ? $this->directory : ''; + return $this->autoRouter->directory(); } /** @@ -309,10 +323,12 @@ public function setIndexPage($page): self /** * Tells the system whether we should translate URI dashes or not * in the URI from a dash to an underscore. + * + * @deprecated Moved to AutoRouter class. */ public function setTranslateURIDashes(bool $val = false): self { - $this->translateURIDashes = $val; + $this->autoRouter->setTranslateURIDashes($val); return $this; } @@ -467,78 +483,15 @@ protected function checkRoutes(string $uri): bool } /** + * Checks Auto Routs. + * * Attempts to match a URI path against Controllers and directories * found in APPPATH/Controllers, to find a matching route. */ public function autoRoute(string $uri) { - $segments = explode('/', $uri); - - // WARNING: Directories get shifted out of the segments array. - $segments = $this->scanControllers($segments); - - // If we don't have any segments left - use the default controller; - // If not empty, then the first segment should be the controller - if (! empty($segments)) { - $this->controller = ucfirst(array_shift($segments)); - } - - $controllerName = $this->controllerName(); - if (! $this->isValidSegment($controllerName)) { - throw new PageNotFoundException($this->controller . ' is not a valid controller name'); - } - - // Use the method name if it exists. - // If it doesn't, no biggie - the default method name - // has already been set. - if (! empty($segments)) { - $this->method = array_shift($segments) ?: $this->method; - } - - // Prevent access to initController method - if (strtolower($this->method) === 'initcontroller') { - throw PageNotFoundException::forPageNotFound(); - } - - if (! empty($segments)) { - $this->params = $segments; - } - - $defaultNamespace = $this->collection->getDefaultNamespace(); - if ($this->collection->getHTTPVerb() !== 'cli') { - $controller = '\\' . $defaultNamespace; - - $controller .= $this->directory ? str_replace('/', '\\', $this->directory) : ''; - $controller .= $controllerName; - - $controller = strtolower($controller); - $methodName = strtolower($this->methodName()); - - foreach ($this->collection->getRoutes('cli') as $route) { - if (is_string($route)) { - $route = strtolower($route); - if (strpos($route, $controller . '::' . $methodName) === 0) { - throw new PageNotFoundException(); - } - - if ($route === $controller) { - throw new PageNotFoundException(); - } - } - } - } - - // Load the file so that it's available for CodeIgniter. - $file = APPPATH . 'Controllers/' . $this->directory . $controllerName . '.php'; - if (is_file($file)) { - include_once $file; - } - - // Ensure the controller stores the fully-qualified class name - // We have to check for a length over 1, since by default it will be '\' - if (strpos($this->controller, '\\') === false && strlen($defaultNamespace) > 1) { - $this->controller = '\\' . ltrim(str_replace('/', '\\', $defaultNamespace . $this->directory . $controllerName), '\\'); - } + [$this->directory, $this->controller, $this->method, $this->params] + = $this->autoRouter->getRoute($uri); } /** @@ -563,6 +516,8 @@ protected function validateRequest(array $segments): array * @param array $segments URI segments * * @return array returns an array of remaining uri segments that don't map onto a directory + * + * @deprecated Not used. Moved to AutoRouter class. */ protected function scanControllers(array $segments): array { @@ -607,6 +562,8 @@ protected function scanControllers(array $segments): array * Sets the sub-directory that the controller is in. * * @param bool $validate if true, checks to make sure $dir consists of only PSR4 compliant segments + * + * @deprecated Moved to AutoRouter class. */ public function setDirectory(?string $dir = null, bool $append = false, bool $validate = true) { @@ -616,27 +573,15 @@ public function setDirectory(?string $dir = null, bool $append = false, bool $va return; } - if ($validate) { - $segments = explode('/', trim($dir, '/')); - - foreach ($segments as $segment) { - if (! $this->isValidSegment($segment)) { - return; - } - } - } - - if ($append !== true || empty($this->directory)) { - $this->directory = trim($dir, '/') . '/'; - } else { - $this->directory .= trim($dir, '/') . '/'; - } + $this->autoRouter->setDirectory($dir, $append, $validate); } /** * Returns true if the supplied $segment string represents a valid PSR-4 compliant namespace/directory segment * * regex comes from https://www.php.net/manual/en/language.variables.basics.php + * + * @deprecated Moved to AutoRouter class. */ private function isValidSegment(string $segment): bool { From eabb12c9cce9cc724977b0619638ec039eefce1a Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 8 Apr 2022 14:26:52 +0900 Subject: [PATCH 05/18] style: break long lines --- system/Router/AutoRouter.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index 3d43ffb28209..149811149cf1 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -149,7 +149,14 @@ public function getRoute(string $uri): array // Ensure the controller stores the fully-qualified class name // We have to check for a length over 1, since by default it will be '\' if (strpos($this->controller, '\\') === false && strlen($this->defaultNamespace) > 1) { - $this->controller = '\\' . ltrim(str_replace('/', '\\', $this->defaultNamespace . $this->directory . $controllerName), '\\'); + $this->controller = '\\' . ltrim( + str_replace( + '/', + '\\', + $this->defaultNamespace . $this->directory . $controllerName + ), + '\\' + ); } return [$this->directory, $this->controllerName(), $this->methodName(), $this->params]; @@ -189,7 +196,11 @@ protected function scanControllers(array $segments): array $c = count($segments); while ($c-- > 0) { - $segmentConvert = ucfirst($this->translateURIDashes === true ? str_replace('-', '_', $segments[0]) : $segments[0]); + $segmentConvert = ucfirst( + $this->translateURIDashes === true + ? str_replace('-', '_', $segments[0]) + : $segments[0] + ); // as soon as we encounter any segment that is not PSR-4 compliant, stop searching if (! $this->isValidSegment($segmentConvert)) { return $segments; From 43e11ba75eda71720432ed7095a5c440f145eea3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 10:16:38 +0900 Subject: [PATCH 06/18] refactor: remove uneeded `=== true` Co-authored-by: Mostafa Khudair <59371810+mostafakhudair@users.noreply.github.com> --- system/Router/AutoRouter.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index 149811149cf1..a03859141545 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -197,9 +197,7 @@ protected function scanControllers(array $segments): array while ($c-- > 0) { $segmentConvert = ucfirst( - $this->translateURIDashes === true - ? str_replace('-', '_', $segments[0]) - : $segments[0] + $this->translateURIDashes ? str_replace('-', '_', $segments[0]) : $segments[0] ); // as soon as we encounter any segment that is not PSR-4 compliant, stop searching if (! $this->isValidSegment($segmentConvert)) { From dc912e09318c2584e4216f74ee73377e8dd06f09 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 09:46:20 +0900 Subject: [PATCH 07/18] feat: add RouteCollection::getRegisteredControllers() --- phpstan-baseline.neon.dist | 6 +- system/Router/RouteCollection.php | 51 +++++++++++ tests/system/Router/RouteCollectionTest.php | 93 +++++++++++++++++++++ 3 files changed, 147 insertions(+), 3 deletions(-) diff --git a/phpstan-baseline.neon.dist b/phpstan-baseline.neon.dist index 6193a69d7c1c..7887ee6a3b03 100644 --- a/phpstan-baseline.neon.dist +++ b/phpstan-baseline.neon.dist @@ -716,19 +716,19 @@ parameters: path: system/Router/Router.php - - message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + message: "#^Call to an undefined method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getRegisteredControllers\\(\\)\\.$#" count: 1 path: system/Router/Router.php - - message: "#^Method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getRoutes\\(\\) invoked with 1 parameter, 0 required\\.$#" + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" count: 1 path: system/Router/Router.php - message: "#^Method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getRoutes\\(\\) invoked with 1 parameter, 0 required\\.$#" count: 1 - path: system/Router/AutoRouter.php + path: system/Router/Router.php - message: "#^Property Config\\\\App\\:\\:\\$CSRF[a-zA-Z]+ \\([a-zA-Z]+\\) on left side of \\?\\? is not nullable\\.$#" diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index a1dd7b1a8fe7..4bb048991a99 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -105,6 +105,16 @@ class RouteCollection implements RouteCollectionInterface * An array of all routes and their mappings. * * @var array + * + * [ + * verb => [ + * routeName => [ + * 'route' => [ + * routeKey => handler, + * ] + * ] + * ], + * ] */ protected $routes = [ '*' => [], @@ -1403,4 +1413,45 @@ public function setPrioritize(bool $enabled = true) return $this; } + + /** + * Get all controllers in Route Handlers + * + * @param string|null $verb HTTP verb. `'*'` returns all controllers in any verb. + */ + public function getRegisteredControllers(?string $verb = '*'): array + { + $routes = []; + + if ($verb === '*') { + $rawRoutes = []; + + foreach ($this->defaultHTTPMethods as $tmpVerb) { + $rawRoutes = array_merge($rawRoutes, $this->routes[$tmpVerb]); + } + + foreach ($rawRoutes as $route) { + $key = key($route['route']); + $handler = $route['route'][$key]; + + $routes[$key] = $handler; + } + } else { + $routes = $this->getRoutes($verb); + } + + $controllers = []; + + foreach ($routes as $handler) { + if (! is_string($handler)) { + continue; + } + + [$controller] = explode('::', $handler, 2); + + $controllers[] = $controller; + } + + return array_unique($controllers); + } } diff --git a/tests/system/Router/RouteCollectionTest.php b/tests/system/Router/RouteCollectionTest.php index 513658b57642..7eb3f99f3128 100644 --- a/tests/system/Router/RouteCollectionTest.php +++ b/tests/system/Router/RouteCollectionTest.php @@ -1674,4 +1674,97 @@ public function testRoutePriorityValue() $collection->add('string-negative-integer', 'Controller::method', ['priority' => '-1']); $this->assertSame(1, $collection->getRoutesOptions('string-negative-integer')['priority']); } + + public function testGetRegisteredControllersReturnsControllerForHTTPverb() + { + $collection = $this->getCollector(); + $collection->get('test', '\App\Controllers\Hello::get'); + $collection->post('test', '\App\Controllers\Hello::post'); + + $routes = $collection->getRegisteredControllers('get'); + + $expects = [ + '\App\Controllers\Hello', + ]; + $this->assertSame($expects, $routes); + + $routes = $collection->getRegisteredControllers('post'); + + $expects = [ + '\App\Controllers\Hello', + ]; + $this->assertSame($expects, $routes); + } + + public function testGetRegisteredControllersReturnsTwoControllers() + { + $collection = $this->getCollector(); + $collection->post('test', '\App\Controllers\Test::post'); + $collection->post('hello', '\App\Controllers\Hello::post'); + + $routes = $collection->getRegisteredControllers('post'); + + $expects = [ + '\App\Controllers\Test', + '\App\Controllers\Hello', + ]; + $this->assertSame($expects, $routes); + } + + public function testGetRegisteredControllersReturnsOneControllerWhenTwoRoutsWithDiffernetMethods() + { + $collection = $this->getCollector(); + $collection->post('test', '\App\Controllers\Test::test'); + $collection->post('hello', '\App\Controllers\Test::hello'); + + $routes = $collection->getRegisteredControllers('post'); + + $expects = [ + '\App\Controllers\Test', + ]; + $this->assertSame($expects, $routes); + } + + public function testGetRegisteredControllersReturnsAllControllers() + { + $collection = $this->getCollector(); + $collection->get('test', '\App\Controllers\Hello::get'); + $collection->post('test', '\App\Controllers\Hello::post'); + $collection->post('hello', '\App\Controllers\Test::hello'); + + $routes = $collection->getRegisteredControllers('*'); + + $expects = [ + '\App\Controllers\Hello', + '\App\Controllers\Test', + ]; + $this->assertSame($expects, $routes); + } + + public function testGetRegisteredControllersReturnsControllerByAddMethod() + { + $collection = $this->getCollector(); + $collection->get('test', '\App\Controllers\Hello::get'); + $collection->add('hello', '\App\Controllers\Test::hello'); + + $routes = $collection->getRegisteredControllers('get'); + + $expects = [ + '\App\Controllers\Hello', + '\App\Controllers\Test', + ]; + $this->assertSame($expects, $routes); + } + + public function testGetRegisteredControllersDoesNotReturnClosures() + { + $collection = $this->getCollector(); + $collection->get('feed', static function () { + }); + + $routes = $collection->getRegisteredControllers('*'); + + $expects = []; + $this->assertSame($expects, $routes); + } } From c8169d68e4535d110d86d5c803fa81210a40d1a2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 10:11:15 +0900 Subject: [PATCH 08/18] fix: can access controller by auto-routing when adding cli route with placeholder as a method Example: $routes->setAutoRoute(true); $routes->cli('hello/(:segment)', 'Home::$1'); Navigate to http://localhost:8080/home - remove RouteCollection in AutoRouter - improve TestCase --- system/Router/AutoRouter.php | 35 +++++++++++----------- system/Router/Router.php | 4 ++- tests/system/Test/FeatureTestTraitTest.php | 24 +++++++++++---- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index a03859141545..465d7ad47bb1 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -19,9 +19,9 @@ class AutoRouter { /** - * A RouteCollection instance. + * Controller list that can't be accessible. */ - protected RouteCollectionInterface $collection; + protected array $protectedControllers; /** * Sub-directory that contains the requested controller class. @@ -61,18 +61,20 @@ class AutoRouter protected string $defaultNamespace; public function __construct( - RouteCollectionInterface $routes, + array $protectedControllers, string $defaultNamespace, + string $defaultController, + string $defaultMethod, bool $translateURIDashes, string $httpVerb ) { - $this->collection = $routes; - $this->defaultNamespace = $defaultNamespace; - $this->translateURIDashes = $translateURIDashes; - $this->httpVerb = $httpVerb; + $this->protectedControllers = $protectedControllers; + $this->defaultNamespace = $defaultNamespace; + $this->translateURIDashes = $translateURIDashes; + $this->httpVerb = $httpVerb; - $this->controller = $this->collection->getDefaultController(); - $this->method = $this->collection->getDefaultMethod(); + $this->controller = $defaultController; + $this->method = $defaultMethod; } /** @@ -126,15 +128,12 @@ public function getRoute(string $uri): array $controller = strtolower($controller); $methodName = strtolower($this->methodName()); - foreach ($this->collection->getRoutes('cli') as $route) { - if (is_string($route)) { - $route = strtolower($route); - if (strpos($route, $controller . '::' . $methodName) === 0) { - throw new PageNotFoundException(); - } - - if ($route === $controller) { - throw new PageNotFoundException(); + foreach ($this->protectedControllers as $controllerInRoute) { + if (is_string($controllerInRoute)) { + if (strtolower($controllerInRoute) === $controller) { + throw new PageNotFoundException( + 'Cannot access the controller in a CLI Route. Controller: ' . $controllerInRoute + ); } } } diff --git a/system/Router/Router.php b/system/Router/Router.php index 673d95bc1d9b..2d2314cce5d7 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -134,8 +134,10 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request $this->translateURIDashes = $this->collection->shouldTranslateURIDashes(); $this->autoRouter = new AutoRouter( - $this->collection, + $this->collection->getRegisteredControllers('cli'), $this->collection->getDefaultNamespace(), + $this->collection->getDefaultController(), + $this->collection->getDefaultMethod(), $this->translateURIDashes, $this->collection->getHTTPVerb() ); diff --git a/tests/system/Test/FeatureTestTraitTest.php b/tests/system/Test/FeatureTestTraitTest.php index 0a424aff4ad2..7fcb7cb13fe1 100644 --- a/tests/system/Test/FeatureTestTraitTest.php +++ b/tests/system/Test/FeatureTestTraitTest.php @@ -13,11 +13,10 @@ use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\Response; +use Config\Services; /** - * @group DatabaseLive - * @runTestsInSeparateProcesses - * @preserveGlobalState disabled + * @group DatabaseLive * * @internal */ @@ -32,6 +31,13 @@ protected function setUp(): void $this->skipEvents(); } + protected function tearDown(): void + { + parent::tearDown(); + + $this->resetServices(); + } + public function testCallGet() { $this->withRoutes([ @@ -253,11 +259,16 @@ public function provideRoutesData() 'Hello::index', 'Hello', ], - 'parameterized cli' => [ + 'parameterized param cli' => [ 'hello/(:any)', 'Hello::index/$1', 'Hello/index/samsonasik', ], + 'parameterized method cli' => [ + 'hello/(:segment)', + 'Hello::$1', + 'Hello/index', + ], 'default method index' => [ 'hello', 'Hello', @@ -281,8 +292,11 @@ public function provideRoutesData() public function testOpenCliRoutesFromHttpGot404($from, $to, $httpGet) { $this->expectException(PageNotFoundException::class); + $this->expectExceptionMessage('Cannot access the controller in a CLI Route.'); - require_once SUPPORTPATH . 'Controllers/Hello.php'; + $collection = Services::routes(); + $collection->setAutoRoute(true); + $collection->setDefaultNamespace('Tests\Support\Controllers'); $this->withRoutes([ [ From 0e42760154ad7e53fdb2c85e2c1dbbb9cae90472 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 10:25:26 +0900 Subject: [PATCH 09/18] docs: remove @deprecated This method is used by Toolbar. --- system/Router/Router.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/system/Router/Router.php b/system/Router/Router.php index 2d2314cce5d7..ae42f56574e8 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -278,8 +278,6 @@ public function params(): array * if any. Relative to APPPATH.'Controllers'. * * Only used when auto-routing is turned on. - * - * @deprecated Moved to AutoRouter class. */ public function directory(): string { From 98cc70cfc715b90e9af46c6f52e3420f7f40ac21 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 10:26:57 +0900 Subject: [PATCH 10/18] refactor: add property type --- system/Router/Router.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/system/Router/Router.php b/system/Router/Router.php index ae42f56574e8..e3d84c06235a 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -113,10 +113,7 @@ class Router implements RouterInterface */ protected $filtersInfo = []; - /** - * @var AutoRouter|null - */ - protected $autoRouter; + protected ?AutoRouter $autoRouter; /** * Stores a reference to the RouteCollection object. From 74a0364992897a3e002a57268d081605ef3cad48 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 10:44:15 +0900 Subject: [PATCH 11/18] refactor: instantiate AutoRouter only when auto routing is enabled --- system/Router/Router.php | 32 +++++++++++++++++++++--------- tests/system/Router/RouterTest.php | 21 +++++++++++++++++++- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/system/Router/Router.php b/system/Router/Router.php index e3d84c06235a..1b9ec718ff11 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -113,7 +113,7 @@ class Router implements RouterInterface */ protected $filtersInfo = []; - protected ?AutoRouter $autoRouter; + protected ?AutoRouter $autoRouter = null; /** * Stores a reference to the RouteCollection object. @@ -130,14 +130,16 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request $this->translateURIDashes = $this->collection->shouldTranslateURIDashes(); - $this->autoRouter = new AutoRouter( - $this->collection->getRegisteredControllers('cli'), - $this->collection->getDefaultNamespace(), - $this->collection->getDefaultController(), - $this->collection->getDefaultMethod(), - $this->translateURIDashes, - $this->collection->getHTTPVerb() - ); + if ($this->collection->shouldAutoRoute()) { + $this->autoRouter = new AutoRouter( + $this->collection->getRegisteredControllers('cli'), + $this->collection->getDefaultNamespace(), + $this->collection->getDefaultController(), + $this->collection->getDefaultMethod(), + $this->translateURIDashes, + $this->collection->getHTTPVerb() + ); + } } /** @@ -278,6 +280,10 @@ public function params(): array */ public function directory(): string { + if ($this->autoRouter === null) { + return ''; + } + return $this->autoRouter->directory(); } @@ -325,6 +331,10 @@ public function setIndexPage($page): self */ public function setTranslateURIDashes(bool $val = false): self { + if ($this->autoRouter === null) { + return $this; + } + $this->autoRouter->setTranslateURIDashes($val); return $this; @@ -570,6 +580,10 @@ public function setDirectory(?string $dir = null, bool $append = false, bool $va return; } + if ($this->autoRouter === null) { + return; + } + $this->autoRouter->setDirectory($dir, $append, $validate); } diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index 38eb9b7ba48c..be65a9d41460 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -200,6 +200,7 @@ public function testClosures() public function testAutoRouteFindsDefaultControllerAndMethod() { + $this->collection->setAutoRoute(true); $this->collection->setDefaultController('Test'); $this->collection->setDefaultMethod('test'); $router = new Router($this->collection, $this->request); @@ -212,6 +213,7 @@ public function testAutoRouteFindsDefaultControllerAndMethod() public function testAutoRouteFindsControllerWithFileAndMethod() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->autoRoute('myController/someMethod'); @@ -222,6 +224,7 @@ public function testAutoRouteFindsControllerWithFileAndMethod() public function testAutoRouteFindsControllerWithFile() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->autoRoute('myController'); @@ -232,6 +235,7 @@ public function testAutoRouteFindsControllerWithFile() public function testAutoRouteFindsControllerWithSubfolder() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); mkdir(APPPATH . 'Controllers/Subfolder'); @@ -246,6 +250,7 @@ public function testAutoRouteFindsControllerWithSubfolder() public function testAutoRouteFindsDashedSubfolder() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -262,6 +267,7 @@ public function testAutoRouteFindsDashedSubfolder() public function testAutoRouteFindsDashedController() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -280,6 +286,7 @@ public function testAutoRouteFindsDashedController() public function testAutoRouteFindsDashedMethod() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -298,6 +305,7 @@ public function testAutoRouteFindsDashedMethod() public function testAutoRouteFindsDefaultDashFolder() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -314,6 +322,7 @@ public function testAutoRouteFindsDefaultDashFolder() public function testAutoRouteFindsMByteDir() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -330,6 +339,7 @@ public function testAutoRouteFindsMByteDir() public function testAutoRouteFindsMByteController() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -345,6 +355,7 @@ public function testAutoRouteFindsMByteController() public function testAutoRouteRejectsSingleDot() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -355,6 +366,7 @@ public function testAutoRouteRejectsSingleDot() public function testAutoRouteRejectsDoubleDot() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -365,6 +377,7 @@ public function testAutoRouteRejectsDoubleDot() public function testAutoRouteRejectsMidDot() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -375,6 +388,7 @@ public function testAutoRouteRejectsMidDot() public function testAutoRouteRejectsInitController() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -706,6 +720,7 @@ public function testTranslateURIDashesForParams() */ public function testTranslateURIDashesForAutoRoute() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setTranslateURIDashes(true); @@ -720,6 +735,7 @@ public function testTranslateURIDashesForAutoRoute() */ public function testAutoRouteMatchesZeroParams() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->autoRoute('myController/someMethod/0/abc'); @@ -739,6 +755,7 @@ public function testAutoRouteMatchesZeroParams() */ public function testAutoRouteMethodEmpty() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $this->collection->setAutoRoute(true); @@ -789,8 +806,8 @@ public function testRegularExpressionPlaceholderWithUnicode() public function testRouterPriorDirectory() { - $router = new Router($this->collection, $this->request); $this->collection->setAutoRoute(true); + $router = new Router($this->collection, $this->request); $router->setDirectory('foo/bar/baz', false, true); $router->handle('Some_controller/some_method/param1/param2/param3'); @@ -802,6 +819,7 @@ public function testRouterPriorDirectory() public function testSetDirectoryValid() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setDirectory('foo/bar/baz', false, true); @@ -810,6 +828,7 @@ public function testSetDirectoryValid() public function testSetDirectoryInvalid() { + $this->collection->setAutoRoute(true); $router = new Router($this->collection, $this->request); $router->setDirectory('foo/bad-segment/bar', false, true); From a98bde1b2d5ebf43511e6ae501875b750dd0bd2b Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 13:08:53 +0900 Subject: [PATCH 12/18] refactor: remove unneeded $this->params --- system/Router/AutoRouter.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index 465d7ad47bb1..1e5cf7af5a52 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -39,11 +39,6 @@ class AutoRouter */ protected string $method; - /** - * An array of params to the controller method. - */ - protected array $params = []; - /** * Whether dashes in URI's should be converted * to underscores when determining method names. @@ -114,8 +109,11 @@ public function getRoute(string $uri): array throw PageNotFoundException::forPageNotFound(); } + /** @var array $params An array of params to the controller method. */ + $params = []; + if (! empty($segments)) { - $this->params = $segments; + $params = $segments; } // Ensure routes registered via $routes->cli() are not accessible via web. @@ -158,7 +156,7 @@ public function getRoute(string $uri): array ); } - return [$this->directory, $this->controllerName(), $this->methodName(), $this->params]; + return [$this->directory, $this->controllerName(), $this->methodName(), $params]; } /** From 2c331f56672169465c1c59bba33d48f606644378 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 11 Apr 2022 17:02:40 +0900 Subject: [PATCH 13/18] refactor: run rector and php-cs-fixer --- system/Router/AutoRouter.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index 1e5cf7af5a52..7de96f33205a 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -127,13 +127,16 @@ public function getRoute(string $uri): array $methodName = strtolower($this->methodName()); foreach ($this->protectedControllers as $controllerInRoute) { - if (is_string($controllerInRoute)) { - if (strtolower($controllerInRoute) === $controller) { - throw new PageNotFoundException( - 'Cannot access the controller in a CLI Route. Controller: ' . $controllerInRoute - ); - } + if (! is_string($controllerInRoute)) { + continue; } + if (strtolower($controllerInRoute) !== $controller) { + continue; + } + + throw new PageNotFoundException( + 'Cannot access the controller in a CLI Route. Controller: ' . $controllerInRoute + ); } } From 000c115bca7f3370b9148153cbcea5c3a3acd825 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 12 Apr 2022 11:17:11 +0900 Subject: [PATCH 14/18] test: make Event simulate false in FeatureTestTraitTest tearDown() In setUp() Event simulate is set to true, so it should be restored in tearDown(). --- tests/system/Test/FeatureTestTraitTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/system/Test/FeatureTestTraitTest.php b/tests/system/Test/FeatureTestTraitTest.php index 7fcb7cb13fe1..4636d58d3927 100644 --- a/tests/system/Test/FeatureTestTraitTest.php +++ b/tests/system/Test/FeatureTestTraitTest.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Test; +use CodeIgniter\Events\Events; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\Response; use Config\Services; @@ -35,6 +36,8 @@ protected function tearDown(): void { parent::tearDown(); + Events::simulate(false); + $this->resetServices(); } From 1ad510b61da47dd72c9bb4cb12a70ed75c45c4ea Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 12 Apr 2022 11:24:26 +0900 Subject: [PATCH 15/18] test: fix MigrationRunnerTest --- .../Database/Migrations/MigrationRunnerTest.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/system/Database/Migrations/MigrationRunnerTest.php b/tests/system/Database/Migrations/MigrationRunnerTest.php index e97e4e38e56c..774137bc0f76 100644 --- a/tests/system/Database/Migrations/MigrationRunnerTest.php +++ b/tests/system/Database/Migrations/MigrationRunnerTest.php @@ -33,6 +33,12 @@ final class MigrationRunnerTest extends CIUnitTestCase use DatabaseTestTrait; protected $refresh = true; + + // Do not migrate automatically, because we test migrations. + protected $migrate = false; + + // Use specific migration files for this test case. + protected $namespace = 'Tests\Support\MigrationTestMigrations'; protected $root; protected $start; protected $config; @@ -62,6 +68,8 @@ protected function tearDown(): void { parent::tearDown(); + // To delete data with `$this->regressDatabase()`, set it true. + $this->migrate = true; $this->regressDatabase(); } @@ -342,10 +350,11 @@ public function testLatestTriggersEvent() { $runner = new MigrationRunner($this->config); $runner->setSilent(false) - ->setNamespace('Tests\Support\MigrationTestMigrations') - ->clearHistory(); + ->setNamespace('Tests\Support\MigrationTestMigrations'); $result = null; + + Events::removeAllListeners(); Events::on('migrate', static function ($arg) use (&$result) { $result = $arg; }); @@ -360,10 +369,10 @@ public function testRegressTriggersEvent() { $runner = new MigrationRunner($this->config); $runner->setSilent(false) - ->setNamespace('Tests\Support\MigrationTestMigrations') - ->clearHistory(); + ->setNamespace('Tests\Support\MigrationTestMigrations'); $result = null; + Events::removeAllListeners(); Events::on('migrate', static function ($arg) use (&$result) { $result = $arg; }); From 72ef96808bb81e23f1217bc7c08678e5ebb56e1e Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 19 Apr 2022 13:36:11 +0900 Subject: [PATCH 16/18] docs: improve doc comment Co-authored-by: John Paul E. Balandan, CPA <51850998+paulbalandan@users.noreply.github.com> --- system/Router/AutoRouter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index 7de96f33205a..b1e14a2aa7f0 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -19,7 +19,7 @@ class AutoRouter { /** - * Controller list that can't be accessible. + * List of controllers registered for the CLI verb that should not be accessed in the web. */ protected array $protectedControllers; From d899e0b71fef6a39f70a0a62209b052c886f3008 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Apr 2022 14:58:33 +0900 Subject: [PATCH 17/18] refactor: remove unused variable --- system/Router/AutoRouter.php | 1 - 1 file changed, 1 deletion(-) diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index b1e14a2aa7f0..bd55d7fae39c 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -124,7 +124,6 @@ public function getRoute(string $uri): array $controller .= $controllerName; $controller = strtolower($controller); - $methodName = strtolower($this->methodName()); foreach ($this->protectedControllers as $controllerInRoute) { if (! is_string($controllerInRoute)) { From 3b405ad421fbb385619b984d7b027776c919bce6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 20 Apr 2022 15:10:26 +0900 Subject: [PATCH 18/18] docs: add changelog --- user_guide_src/source/changelogs/v4.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst index 449ad50f970d..67477d37cdf8 100644 --- a/user_guide_src/source/changelogs/v4.2.0.rst +++ b/user_guide_src/source/changelogs/v4.2.0.rst @@ -19,6 +19,7 @@ BREAKING - The method signature of ``CodeIgniter\CLI\CommandRunner::_remap()`` has been changed to fix a bug. - The ``CodeIgniter\Autoloader\Autoloader::initialize()`` has changed the behavior to fix a bug. It used to use Composer classmap only when ``$modules->discoverInComposer`` is true. Now it always uses the Composer classmap if Composer is available. - The color code output by :ref:`CLI::color() ` has been changed to fix a bug. +- To prevent unexpected access from the web browser, if a controller is added to a cli route (``$routes->cli()``), all methods of that controller are no longer accessible via auto routing. Enhancements ************