add trashes
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* 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 CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Router for Auto-Routing
|
||||
*/
|
||||
final class AutoRouter implements AutoRouterInterface
|
||||
{
|
||||
/**
|
||||
* Sub-directory that contains the requested controller class.
|
||||
* Primarily used by 'autoRoute'.
|
||||
*/
|
||||
private ?string $directory = null;
|
||||
|
||||
public function __construct(
|
||||
/**
|
||||
* List of CLI routes that do not contain '*' routes.
|
||||
*
|
||||
* @var array<string, (Closure(mixed...): (ResponseInterface|string|void))|string> [routeKey => handler]
|
||||
*/
|
||||
private readonly array $cliRoutes,
|
||||
/**
|
||||
* Default namespace for controllers.
|
||||
*/
|
||||
private readonly string $defaultNamespace,
|
||||
/**
|
||||
* The name of the controller class.
|
||||
*/
|
||||
private string $controller,
|
||||
/**
|
||||
* The name of the method to use.
|
||||
*/
|
||||
private string $method,
|
||||
/**
|
||||
* Whether dashes in URI's should be converted
|
||||
* to underscores when determining method names.
|
||||
*/
|
||||
private bool $translateURIDashes,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to match a URI path against Controllers and directories
|
||||
* found in APPPATH/Controllers, to find a matching route.
|
||||
*
|
||||
* @param string $httpVerb HTTP verb like `GET`,`POST`
|
||||
*
|
||||
* @return array [directory_name, controller_name, controller_method, params]
|
||||
*/
|
||||
public function getRoute(string $uri, string $httpVerb): 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 ($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 ($segments !== []) {
|
||||
$this->method = array_shift($segments) ?: $this->method;
|
||||
}
|
||||
|
||||
// Prevent access to initController method
|
||||
if (strtolower($this->method) === 'initcontroller') {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
/** @var array $params An array of params to the controller method. */
|
||||
$params = [];
|
||||
|
||||
if ($segments !== []) {
|
||||
$params = $segments;
|
||||
}
|
||||
|
||||
// Ensure routes registered via $routes->cli() are not accessible via web.
|
||||
if ($httpVerb !== 'CLI') {
|
||||
$controller = '\\' . $this->defaultNamespace;
|
||||
|
||||
$controller .= $this->directory !== null ? str_replace('/', '\\', $this->directory) : '';
|
||||
$controller .= $controllerName;
|
||||
|
||||
$controller = strtolower($controller);
|
||||
$methodName = strtolower($this->methodName());
|
||||
|
||||
foreach ($this->cliRoutes as $handler) {
|
||||
if (is_string($handler)) {
|
||||
$handler = strtolower($handler);
|
||||
|
||||
// Like $routes->cli('hello/(:segment)', 'Home::$1')
|
||||
if (str_contains($handler, '::$')) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access CLI Route: ' . $uri,
|
||||
);
|
||||
}
|
||||
|
||||
if (str_starts_with($handler, $controller . '::' . $methodName)) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access CLI Route: ' . $uri,
|
||||
);
|
||||
}
|
||||
|
||||
if ($handler === $controller) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access CLI Route: ' . $uri,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the file so that it's available for CodeIgniter.
|
||||
$file = APPPATH . 'Controllers/' . $this->directory . $controllerName . '.php';
|
||||
|
||||
if (! is_file($file)) {
|
||||
throw PageNotFoundException::forControllerNotFound($this->controller, $this->method);
|
||||
}
|
||||
|
||||
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 (! str_contains($this->controller, '\\') && strlen($this->defaultNamespace) > 1) {
|
||||
$this->controller = '\\' . ltrim(
|
||||
str_replace(
|
||||
'/',
|
||||
'\\',
|
||||
$this->defaultNamespace . $this->directory . $controllerName,
|
||||
),
|
||||
'\\',
|
||||
);
|
||||
}
|
||||
|
||||
return [$this->directory, $this->controllerName(), $this->methodName(), $params];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the system whether we should translate URI dashes or not
|
||||
* in the URI from a dash to an underscore.
|
||||
*
|
||||
* @deprecated This method should be removed.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
private function scanControllers(array $segments): array
|
||||
{
|
||||
$segments = array_filter($segments, static fn ($segment): bool => $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 ? 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
|
||||
*
|
||||
* @deprecated This method should be removed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDirectory(?string $dir = null, bool $append = false, bool $validate = true)
|
||||
{
|
||||
if ((string) $dir === '') {
|
||||
$this->directory = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($validate) {
|
||||
$segments = explode('/', trim($dir, '/'));
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
if (! $this->isValidSegment($segment)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $append || ((string) $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'.
|
||||
*
|
||||
* @deprecated This method should be removed.
|
||||
*/
|
||||
public function directory(): string
|
||||
{
|
||||
return ((string) $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* 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;
|
||||
use CodeIgniter\Router\Exceptions\MethodNotFoundException;
|
||||
use Config\Routing;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* New Secure Router for Auto-Routing
|
||||
*
|
||||
* @see \CodeIgniter\Router\AutoRouterImprovedTest
|
||||
*/
|
||||
final class AutoRouterImproved implements AutoRouterInterface
|
||||
{
|
||||
/**
|
||||
* Sub-directory that contains the requested controller class.
|
||||
*/
|
||||
private ?string $directory = null;
|
||||
|
||||
/**
|
||||
* The name of the controller class.
|
||||
*/
|
||||
private string $controller;
|
||||
|
||||
/**
|
||||
* The name of the method to use.
|
||||
*/
|
||||
private string $method;
|
||||
|
||||
/**
|
||||
* An array of params to the controller method.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $params = [];
|
||||
|
||||
/**
|
||||
* Whether to translate dashes in URIs for controller/method to CamelCase.
|
||||
* E.g., blog-controller -> BlogController
|
||||
*/
|
||||
private readonly bool $translateUriToCamelCase;
|
||||
|
||||
/**
|
||||
* The namespace for controllers.
|
||||
*/
|
||||
private string $namespace;
|
||||
|
||||
/**
|
||||
* Map of URI segments and namespaces.
|
||||
*
|
||||
* The key is the first URI segment. The value is the controller namespace.
|
||||
* E.g.,
|
||||
* [
|
||||
* 'blog' => 'Acme\Blog\Controllers',
|
||||
* ]
|
||||
*
|
||||
* @var array [ uri_segment => namespace ]
|
||||
*/
|
||||
private array $moduleRoutes;
|
||||
|
||||
/**
|
||||
* The URI segments.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $segments = [];
|
||||
|
||||
/**
|
||||
* The position of the Controller in the URI segments.
|
||||
* Null for the default controller.
|
||||
*/
|
||||
private ?int $controllerPos = null;
|
||||
|
||||
/**
|
||||
* The position of the Method in the URI segments.
|
||||
* Null for the default method.
|
||||
*/
|
||||
private ?int $methodPos = null;
|
||||
|
||||
/**
|
||||
* The position of the first Parameter in the URI segments.
|
||||
* Null for the no parameters.
|
||||
*/
|
||||
private ?int $paramPos = null;
|
||||
|
||||
/**
|
||||
* The current URI
|
||||
*/
|
||||
private ?string $uri = null;
|
||||
|
||||
/**
|
||||
* @param list<class-string> $protectedControllers
|
||||
* @param string $defaultController Short classname
|
||||
*/
|
||||
public function __construct(
|
||||
/**
|
||||
* List of controllers in Defined Routes that should not be accessed via this Auto-Routing.
|
||||
*/
|
||||
private readonly array $protectedControllers,
|
||||
string $namespace,
|
||||
private readonly string $defaultController,
|
||||
/**
|
||||
* The name of the default method without HTTP verb prefix.
|
||||
*/
|
||||
private readonly string $defaultMethod,
|
||||
/**
|
||||
* Whether dashes in URI's should be converted
|
||||
* to underscores when determining method names.
|
||||
*/
|
||||
private readonly bool $translateURIDashes,
|
||||
) {
|
||||
$this->namespace = rtrim($namespace, '\\');
|
||||
|
||||
$routingConfig = config(Routing::class);
|
||||
$this->moduleRoutes = $routingConfig->moduleRoutes;
|
||||
$this->translateUriToCamelCase = $routingConfig->translateUriToCamelCase;
|
||||
|
||||
// Set the default values
|
||||
$this->controller = $this->defaultController;
|
||||
}
|
||||
|
||||
private function createSegments(string $uri): array
|
||||
{
|
||||
$segments = explode('/', $uri);
|
||||
$segments = array_filter($segments, static fn ($segment): bool => $segment !== '');
|
||||
|
||||
// numerically reindex the array, removing gaps
|
||||
return array_values($segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first controller corresponding to the URI segment.
|
||||
*
|
||||
* If there is a controller corresponding to the first segment, the search
|
||||
* ends there. The remaining segments are parameters to the controller.
|
||||
*
|
||||
* @return bool true if a controller class is found.
|
||||
*/
|
||||
private function searchFirstController(): bool
|
||||
{
|
||||
$segments = $this->segments;
|
||||
|
||||
$controller = '\\' . $this->namespace;
|
||||
|
||||
$controllerPos = -1;
|
||||
|
||||
while ($segments !== []) {
|
||||
$segment = array_shift($segments);
|
||||
$controllerPos++;
|
||||
|
||||
$class = $this->translateURI($segment);
|
||||
|
||||
// as soon as we encounter any segment that is not PSR-4 compliant, stop searching
|
||||
if (! $this->isValidSegment($class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$controller .= '\\' . $class;
|
||||
|
||||
if (class_exists($controller)) {
|
||||
$this->controller = $controller;
|
||||
$this->controllerPos = $controllerPos;
|
||||
|
||||
$this->checkUriForController($controller);
|
||||
|
||||
// The first item may be a method name.
|
||||
$this->params = $segments;
|
||||
if ($segments !== []) {
|
||||
$this->paramPos = $this->controllerPos + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the last default controller corresponding to the URI segments.
|
||||
*
|
||||
* @return bool true if a controller class is found.
|
||||
*/
|
||||
private function searchLastDefaultController(): bool
|
||||
{
|
||||
$segments = $this->segments;
|
||||
|
||||
$segmentCount = count($this->segments);
|
||||
$paramPos = null;
|
||||
$params = [];
|
||||
|
||||
while ($segments !== []) {
|
||||
if ($segmentCount > count($segments)) {
|
||||
$paramPos = count($segments);
|
||||
}
|
||||
|
||||
$namespaces = array_map(
|
||||
fn ($segment): string => $this->translateURI($segment),
|
||||
$segments,
|
||||
);
|
||||
|
||||
$controller = '\\' . $this->namespace
|
||||
. '\\' . implode('\\', $namespaces)
|
||||
. '\\' . $this->defaultController;
|
||||
|
||||
if (class_exists($controller)) {
|
||||
$this->controller = $controller;
|
||||
$this->params = $params;
|
||||
|
||||
if ($params !== []) {
|
||||
$this->paramPos = $paramPos;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepend the last element in $segments to the beginning of $params.
|
||||
array_unshift($params, array_pop($segments));
|
||||
}
|
||||
|
||||
// Check for the default controller in Controllers directory.
|
||||
$controller = '\\' . $this->namespace
|
||||
. '\\' . $this->defaultController;
|
||||
|
||||
if (class_exists($controller)) {
|
||||
$this->controller = $controller;
|
||||
$this->params = $params;
|
||||
|
||||
if ($params !== []) {
|
||||
$this->paramPos = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds controller, method and params from the URI.
|
||||
*
|
||||
* @param string $httpVerb HTTP verb like `GET`,`POST`
|
||||
*
|
||||
* @return array [directory_name, controller_name, controller_method, params]
|
||||
*/
|
||||
public function getRoute(string $uri, string $httpVerb): array
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$httpVerb = strtolower($httpVerb);
|
||||
|
||||
// Reset Controller method params.
|
||||
$this->params = [];
|
||||
|
||||
$defaultMethod = $httpVerb . ucfirst($this->defaultMethod);
|
||||
$this->method = $defaultMethod;
|
||||
|
||||
$this->segments = $this->createSegments($uri);
|
||||
|
||||
// Check for Module Routes.
|
||||
if (
|
||||
$this->segments !== []
|
||||
&& array_key_exists($this->segments[0], $this->moduleRoutes)
|
||||
) {
|
||||
$uriSegment = array_shift($this->segments);
|
||||
$this->namespace = rtrim($this->moduleRoutes[$uriSegment], '\\');
|
||||
}
|
||||
|
||||
if ($this->searchFirstController()) {
|
||||
// Controller is found.
|
||||
$baseControllerName = class_basename($this->controller);
|
||||
|
||||
// Prevent access to default controller path
|
||||
if (
|
||||
strtolower($baseControllerName) === strtolower($this->defaultController)
|
||||
) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access the default controller "' . $this->controller . '" with the controller name URI path.',
|
||||
);
|
||||
}
|
||||
} elseif ($this->searchLastDefaultController()) {
|
||||
// The default Controller is found.
|
||||
$baseControllerName = class_basename($this->controller);
|
||||
} else {
|
||||
// No Controller is found.
|
||||
throw new PageNotFoundException('No controller is found for: ' . $uri);
|
||||
}
|
||||
|
||||
// The first item may be a method name.
|
||||
/** @var list<string> $params */
|
||||
$params = $this->params;
|
||||
|
||||
$methodParam = array_shift($params);
|
||||
|
||||
$method = '';
|
||||
if ($methodParam !== null) {
|
||||
$method = $httpVerb . $this->translateURI($methodParam);
|
||||
|
||||
$this->checkUriForMethod($method);
|
||||
}
|
||||
|
||||
if ($methodParam !== null && method_exists($this->controller, $method)) {
|
||||
// Method is found.
|
||||
$this->method = $method;
|
||||
$this->params = $params;
|
||||
|
||||
// Update the positions.
|
||||
$this->methodPos = $this->paramPos;
|
||||
if ($params === []) {
|
||||
$this->paramPos = null;
|
||||
}
|
||||
if ($this->paramPos !== null) {
|
||||
$this->paramPos++;
|
||||
}
|
||||
|
||||
// Prevent access to default controller's method
|
||||
if (strtolower($baseControllerName) === strtolower($this->defaultController)) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access the default controller "' . $this->controller . '::' . $this->method . '"',
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent access to default method path
|
||||
if (strtolower($this->method) === strtolower($defaultMethod)) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access the default method "' . $this->method . '" with the method name URI path.',
|
||||
);
|
||||
}
|
||||
} elseif (method_exists($this->controller, $defaultMethod)) {
|
||||
// The default method is found.
|
||||
$this->method = $defaultMethod;
|
||||
} else {
|
||||
// No method is found.
|
||||
throw PageNotFoundException::forControllerNotFound($this->controller, $method);
|
||||
}
|
||||
|
||||
// Ensure the controller is not defined in routes.
|
||||
$this->protectDefinedRoutes();
|
||||
|
||||
// Ensure the controller does not have _remap() method.
|
||||
$this->checkRemap();
|
||||
|
||||
// Ensure the URI segments for the controller and method do not contain
|
||||
// underscores when $translateURIDashes is true.
|
||||
$this->checkUnderscore();
|
||||
|
||||
// Check parameter count
|
||||
try {
|
||||
$this->checkParameters();
|
||||
} catch (MethodNotFoundException) {
|
||||
throw PageNotFoundException::forControllerNotFound($this->controller, $this->method);
|
||||
}
|
||||
|
||||
$this->setDirectory();
|
||||
|
||||
return [$this->directory, $this->controller, $this->method, $this->params];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For test purpose only.
|
||||
*
|
||||
* @return array<string, int|null>
|
||||
*/
|
||||
public function getPos(): array
|
||||
{
|
||||
return [
|
||||
'controller' => $this->controllerPos,
|
||||
'method' => $this->methodPos,
|
||||
'params' => $this->paramPos,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory path from the controller and set it to the property.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setDirectory()
|
||||
{
|
||||
$segments = explode('\\', trim($this->controller, '\\'));
|
||||
|
||||
// Remove short classname.
|
||||
array_pop($segments);
|
||||
|
||||
$namespaces = implode('\\', $segments);
|
||||
|
||||
$dir = str_replace(
|
||||
'\\',
|
||||
'/',
|
||||
ltrim(substr($namespaces, strlen($this->namespace)), '\\'),
|
||||
);
|
||||
|
||||
if ($dir !== '') {
|
||||
$this->directory = $dir . '/';
|
||||
}
|
||||
}
|
||||
|
||||
private function protectDefinedRoutes(): void
|
||||
{
|
||||
$controller = strtolower($this->controller);
|
||||
|
||||
foreach ($this->protectedControllers as $controllerInRoutes) {
|
||||
$routeLowerCase = strtolower($controllerInRoutes);
|
||||
|
||||
if ($routeLowerCase === $controller) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access the controller in Defined Routes. Controller: ' . $controllerInRoutes,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkParameters(): void
|
||||
{
|
||||
try {
|
||||
$refClass = new ReflectionClass($this->controller);
|
||||
} catch (ReflectionException) {
|
||||
throw PageNotFoundException::forControllerNotFound($this->controller, $this->method);
|
||||
}
|
||||
|
||||
try {
|
||||
$refMethod = $refClass->getMethod($this->method);
|
||||
$refParams = $refMethod->getParameters();
|
||||
} catch (ReflectionException) {
|
||||
throw new MethodNotFoundException();
|
||||
}
|
||||
|
||||
if (! $refMethod->isPublic()) {
|
||||
throw new MethodNotFoundException();
|
||||
}
|
||||
|
||||
if (count($refParams) < count($this->params)) {
|
||||
throw new PageNotFoundException(
|
||||
'The param count in the URI are greater than the controller method params.'
|
||||
. ' Handler:' . $this->controller . '::' . $this->method
|
||||
. ', URI:' . $this->uri,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkRemap(): void
|
||||
{
|
||||
try {
|
||||
$refClass = new ReflectionClass($this->controller);
|
||||
$refClass->getMethod('_remap');
|
||||
|
||||
throw new PageNotFoundException(
|
||||
'AutoRouterImproved does not support `_remap()` method.'
|
||||
. ' Controller:' . $this->controller,
|
||||
);
|
||||
} catch (ReflectionException) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
private function checkUnderscore(): void
|
||||
{
|
||||
if ($this->translateURIDashes === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paramPos = $this->paramPos ?? count($this->segments);
|
||||
|
||||
for ($i = 0; $i < $paramPos; $i++) {
|
||||
if (str_contains($this->segments[$i], '_')) {
|
||||
throw new PageNotFoundException(
|
||||
'AutoRouterImproved prohibits access to the URI'
|
||||
. ' containing underscores ("' . $this->segments[$i] . '")'
|
||||
. ' when $translateURIDashes is enabled.'
|
||||
. ' Please use the dash.'
|
||||
. ' Handler:' . $this->controller . '::' . $this->method
|
||||
. ', URI:' . $this->uri,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check URI for controller for $translateUriToCamelCase
|
||||
*
|
||||
* @param string $classname Controller classname that is generated from URI.
|
||||
* The case may be a bit incorrect.
|
||||
*/
|
||||
private function checkUriForController(string $classname): void
|
||||
{
|
||||
if ($this->translateUriToCamelCase === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! in_array(ltrim($classname, '\\'), get_declared_classes(), true)) {
|
||||
throw new PageNotFoundException(
|
||||
'"' . $classname . '" is not found.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check URI for method for $translateUriToCamelCase
|
||||
*
|
||||
* @param string $method Controller method name that is generated from URI.
|
||||
* The case may be a bit incorrect.
|
||||
*/
|
||||
private function checkUriForMethod(string $method): void
|
||||
{
|
||||
if ($this->translateUriToCamelCase === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
// For example, if `getSomeMethod()` exists in the controller, only
|
||||
// the URI `controller/some-method` should be accessible. But if a
|
||||
// visitor navigates to the URI `controller/somemethod`, `getSomemethod()`
|
||||
// will be checked, and `method_exists()` will return true because
|
||||
// method names in PHP are case-insensitive.
|
||||
method_exists($this->controller, $method)
|
||||
// But we do not permit `controller/somemethod`, so check the exact
|
||||
// method name.
|
||||
&& ! in_array($method, get_class_methods($this->controller), true)
|
||||
) {
|
||||
throw new PageNotFoundException(
|
||||
'"' . $this->controller . '::' . $method . '()" is not found.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates URI segment to CamelCase or replaces `-` with `_`.
|
||||
*/
|
||||
private function translateURI(string $segment): string
|
||||
{
|
||||
if ($this->translateUriToCamelCase) {
|
||||
if (strtolower($segment) !== $segment) {
|
||||
throw new PageNotFoundException(
|
||||
'AutoRouterImproved prohibits access to the URI'
|
||||
. ' containing uppercase letters ("' . $segment . '")'
|
||||
. ' when $translateUriToCamelCase is enabled.'
|
||||
. ' Please use the dash.'
|
||||
. ' URI:' . $this->uri,
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains($segment, '--')) {
|
||||
throw new PageNotFoundException(
|
||||
'AutoRouterImproved prohibits access to the URI'
|
||||
. ' containing double dash ("' . $segment . '")'
|
||||
. ' when $translateUriToCamelCase is enabled.'
|
||||
. ' Please use the single dash.'
|
||||
. ' URI:' . $this->uri,
|
||||
);
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
' ',
|
||||
'',
|
||||
ucwords(
|
||||
preg_replace('/[\-]+/', ' ', $segment),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$segment = ucfirst($segment);
|
||||
|
||||
if ($this->translateURIDashes) {
|
||||
return str_replace('-', '_', $segment);
|
||||
}
|
||||
|
||||
return $segment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Router;
|
||||
|
||||
/**
|
||||
* Expected behavior of a AutoRouter.
|
||||
*/
|
||||
interface AutoRouterInterface
|
||||
{
|
||||
/**
|
||||
* Returns controller, method and params from the URI.
|
||||
*
|
||||
* @return array [directory_name, controller_name, controller_method, params]
|
||||
*/
|
||||
public function getRoute(string $uri, string $httpVerb): array;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Collect all defined routes for display.
|
||||
*
|
||||
* @see \CodeIgniter\Router\DefinedRouteCollectorTest
|
||||
*/
|
||||
final class DefinedRouteCollector
|
||||
{
|
||||
public function __construct(private readonly RouteCollectionInterface $routeCollection)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator<array{method: string, route: string, name: string, handler: string}>
|
||||
*/
|
||||
public function collect(): Generator
|
||||
{
|
||||
$methods = Router::HTTP_METHODS;
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$routes = $this->routeCollection->getRoutes($method);
|
||||
|
||||
foreach ($routes as $route => $handler) {
|
||||
// The route key should be a string, but it is stored as an array key,
|
||||
// it might be an integer.
|
||||
$route = (string) $route;
|
||||
|
||||
if (is_string($handler) || $handler instanceof Closure) {
|
||||
if ($handler instanceof Closure) {
|
||||
$view = $this->routeCollection->getRoutesOptions($route, $method)['view'] ?? false;
|
||||
|
||||
$handler = $view ? '(View) ' . $view : '(Closure)';
|
||||
}
|
||||
|
||||
$routeName = $this->routeCollection->getRoutesOptions($route, $method)['as'] ?? $route;
|
||||
|
||||
yield [
|
||||
'method' => $method,
|
||||
'route' => $route,
|
||||
'name' => $routeName,
|
||||
'handler' => $handler,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Router\Exceptions;
|
||||
|
||||
/**
|
||||
* Provides a domain-level interface for broad capture
|
||||
* of all Router-related exceptions.
|
||||
*
|
||||
* catch (\CodeIgniter\Router\Exceptions\ExceptionInterface) { ... }
|
||||
*/
|
||||
interface ExceptionInterface extends \CodeIgniter\Exceptions\ExceptionInterface
|
||||
{
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Router\Exceptions;
|
||||
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class MethodNotFoundException extends RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Router\Exceptions;
|
||||
|
||||
use CodeIgniter\Exceptions\FrameworkException;
|
||||
|
||||
/**
|
||||
* RouterException
|
||||
*/
|
||||
class RouterException extends FrameworkException implements ExceptionInterface
|
||||
{
|
||||
/**
|
||||
* Thrown when the actual parameter type does not match
|
||||
* the expected types.
|
||||
*
|
||||
* @return RouterException
|
||||
*/
|
||||
public static function forInvalidParameterType()
|
||||
{
|
||||
return new static(lang('Router.invalidParameterType'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when a default route is not set.
|
||||
*
|
||||
* @return RouterException
|
||||
*/
|
||||
public static function forMissingDefaultRoute()
|
||||
{
|
||||
return new static(lang('Router.missingDefaultRoute'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw when controller or its method is not found.
|
||||
*
|
||||
* @return RouterException
|
||||
*/
|
||||
public static function forControllerNotFound(string $controller, string $method)
|
||||
{
|
||||
return new static(lang('HTTP.controllerNotFound', [$controller, $method]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw when route is not valid.
|
||||
*
|
||||
* @return RouterException
|
||||
*/
|
||||
public static function forInvalidRoute(string $route)
|
||||
{
|
||||
return new static(lang('HTTP.invalidRoute', [$route]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw when dynamic controller.
|
||||
*
|
||||
* @return RouterException
|
||||
*/
|
||||
public static function forDynamicController(string $handler)
|
||||
{
|
||||
return new static(lang('Router.invalidDynamicController', [$handler]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw when controller name has `/`.
|
||||
*
|
||||
* @return RouterException
|
||||
*/
|
||||
public static function forInvalidControllerName(string $handler)
|
||||
{
|
||||
return new static(lang('Router.invalidControllerName', [$handler]));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+267
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* 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 CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Interface RouteCollectionInterface
|
||||
*
|
||||
* A Route Collection's sole job is to hold a series of routes. The required
|
||||
* number of methods is kept very small on purpose, but implementors may
|
||||
* add a number of additional methods to customize how the routes are defined.
|
||||
*/
|
||||
interface RouteCollectionInterface
|
||||
{
|
||||
/**
|
||||
* Adds a single route to the collection.
|
||||
*
|
||||
* @param string $from The route path (with placeholders or regex)
|
||||
* @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to The route handler
|
||||
* @param array|null $options The route options
|
||||
*
|
||||
* @return RouteCollectionInterface
|
||||
*/
|
||||
public function add(string $from, $to, ?array $options = null);
|
||||
|
||||
/**
|
||||
* Registers a new constraint with the system. Constraints are used
|
||||
* by the routes as placeholders for regular expressions to make defining
|
||||
* the routes more human-friendly.
|
||||
*
|
||||
* You can pass an associative array as $placeholder, and have
|
||||
* multiple placeholders added at once.
|
||||
*
|
||||
* @param array|string $placeholder
|
||||
* @param string|null $pattern The regex pattern
|
||||
*
|
||||
* @return RouteCollectionInterface
|
||||
*/
|
||||
public function addPlaceholder($placeholder, ?string $pattern = null);
|
||||
|
||||
/**
|
||||
* Sets the default namespace to use for Controllers when no other
|
||||
* namespace has been specified.
|
||||
*
|
||||
* @return RouteCollectionInterface
|
||||
*/
|
||||
public function setDefaultNamespace(string $value);
|
||||
|
||||
/**
|
||||
* Returns the default namespace.
|
||||
*/
|
||||
public function getDefaultNamespace(): string;
|
||||
|
||||
/**
|
||||
* Sets the default controller to use when no other controller has been
|
||||
* specified.
|
||||
*
|
||||
* @return RouteCollectionInterface
|
||||
*
|
||||
* @TODO The default controller is only for auto-routing. So this should be
|
||||
* removed in the future.
|
||||
*/
|
||||
public function setDefaultController(string $value);
|
||||
|
||||
/**
|
||||
* Sets the default method to call on the controller when no other
|
||||
* method has been set in the route.
|
||||
*
|
||||
* @return RouteCollectionInterface
|
||||
*/
|
||||
public function setDefaultMethod(string $value);
|
||||
|
||||
/**
|
||||
* Tells the system whether to convert dashes in URI strings into
|
||||
* underscores. In some search engines, including Google, dashes
|
||||
* create more meaning and make it easier for the search engine to
|
||||
* find words and meaning in the URI for better SEO. But it
|
||||
* doesn't work well with PHP method names....
|
||||
*
|
||||
* @return RouteCollectionInterface
|
||||
*
|
||||
* @TODO This method is only for auto-routing. So this should be removed in
|
||||
* the future.
|
||||
*/
|
||||
public function setTranslateURIDashes(bool $value);
|
||||
|
||||
/**
|
||||
* If TRUE, the system will attempt to match the URI against
|
||||
* Controllers by matching each segment against folders/files
|
||||
* in APPPATH/Controllers, when a match wasn't found against
|
||||
* defined routes.
|
||||
*
|
||||
* If FALSE, will stop searching and do NO automatic routing.
|
||||
*
|
||||
* @TODO This method is only for auto-routing. So this should be removed in
|
||||
* the future.
|
||||
*/
|
||||
public function setAutoRoute(bool $value): self;
|
||||
|
||||
/**
|
||||
* Sets the class/method that should be called if routing doesn't
|
||||
* find a match. It can be either a closure or the controller/method
|
||||
* name exactly like a route is defined: Users::index
|
||||
*
|
||||
* This setting is passed to the Router class and handled there.
|
||||
*
|
||||
* @param callable|null $callable
|
||||
*
|
||||
* @TODO This method is not related to the route collection. So this should
|
||||
* be removed in the future.
|
||||
*/
|
||||
public function set404Override($callable = null): self;
|
||||
|
||||
/**
|
||||
* Returns the 404 Override setting, which can be null, a closure
|
||||
* or the controller/string.
|
||||
*
|
||||
* @return (Closure(string): (ResponseInterface|string|void))|string|null
|
||||
*
|
||||
* @TODO This method is not related to the route collection. So this should
|
||||
* be removed in the future.
|
||||
*/
|
||||
public function get404Override();
|
||||
|
||||
/**
|
||||
* Returns the name of the default controller. With Namespace.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @TODO The default controller is only for auto-routing. So this should be
|
||||
* removed in the future.
|
||||
*/
|
||||
public function getDefaultController();
|
||||
|
||||
/**
|
||||
* Returns the name of the default method to use within the controller.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultMethod();
|
||||
|
||||
/**
|
||||
* Returns the current value of the translateURIDashes setting.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @TODO This method is only for auto-routing. So this should be removed in
|
||||
* the future.
|
||||
*/
|
||||
public function shouldTranslateURIDashes();
|
||||
|
||||
/**
|
||||
* Returns the flag that tells whether to autoRoute URI against Controllers.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @TODO This method is only for auto-routing. So this should be removed in
|
||||
* the future.
|
||||
*/
|
||||
public function shouldAutoRoute();
|
||||
|
||||
/**
|
||||
* Returns the raw array of available routes.
|
||||
*
|
||||
* @param non-empty-string|null $verb HTTP verb like `GET`,`POST` or `*` or `CLI`.
|
||||
* @param bool $includeWildcard Whether to include '*' routes.
|
||||
*/
|
||||
public function getRoutes(?string $verb = null, bool $includeWildcard = true): array;
|
||||
|
||||
/**
|
||||
* Returns one or all routes options
|
||||
*
|
||||
* @param string|null $from The route path (with placeholders or regex)
|
||||
* @param string|null $verb HTTP verb like `GET`,`POST` or `*` or `CLI`.
|
||||
*
|
||||
* @return array<string, int|string> [key => value]
|
||||
*/
|
||||
public function getRoutesOptions(?string $from = null, ?string $verb = null): array;
|
||||
|
||||
/**
|
||||
* Sets the current HTTP verb.
|
||||
*
|
||||
* @param string $verb HTTP verb
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHTTPVerb(string $verb);
|
||||
|
||||
/**
|
||||
* Returns the current HTTP Verb being used.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHTTPVerb();
|
||||
|
||||
/**
|
||||
* Attempts to look up a route based on its destination.
|
||||
*
|
||||
* If a route exists:
|
||||
*
|
||||
* 'path/(:any)/(:any)' => 'Controller::method/$1/$2'
|
||||
*
|
||||
* This method allows you to know the Controller and method
|
||||
* and get the route that leads to it.
|
||||
*
|
||||
* // Equals 'path/$param1/$param2'
|
||||
* reverseRoute('Controller::method', $param1, $param2);
|
||||
*
|
||||
* @param string $search Named route or Controller::method
|
||||
* @param int|string ...$params
|
||||
*
|
||||
* @return false|string The route (URI path relative to baseURL) or false if not found.
|
||||
*/
|
||||
public function reverseRoute(string $search, ...$params);
|
||||
|
||||
/**
|
||||
* Determines if the route is a redirecting route.
|
||||
*/
|
||||
public function isRedirect(string $routeKey): bool;
|
||||
|
||||
/**
|
||||
* Grabs the HTTP status code from a redirecting Route.
|
||||
*/
|
||||
public function getRedirectCode(string $routeKey): int;
|
||||
|
||||
/**
|
||||
* Get the flag that limit or not the routes with {locale} placeholder to App::$supportedLocales
|
||||
*/
|
||||
public function shouldUseSupportedLocalesOnly(): bool;
|
||||
|
||||
/**
|
||||
* Checks a route (using the "from") to see if it's filtered or not.
|
||||
*
|
||||
* @param string|null $verb HTTP verb like `GET`,`POST` or `*` or `CLI`.
|
||||
*/
|
||||
public function isFiltered(string $search, ?string $verb = null): bool;
|
||||
|
||||
/**
|
||||
* Returns the filters that should be applied for a single route, along
|
||||
* with any parameters it might have. Parameters are found by splitting
|
||||
* the parameter name on a colon to separate the filter name from the parameter list,
|
||||
* and the splitting the result on commas. So:
|
||||
*
|
||||
* 'role:admin,manager'
|
||||
*
|
||||
* has a filter of "role", with parameters of ['admin', 'manager'].
|
||||
*
|
||||
* @param string $search routeKey
|
||||
* @param string|null $verb HTTP verb like `GET`,`POST` or `*` or `CLI`.
|
||||
*
|
||||
* @return list<string> filter_name or filter_name:arguments like 'role:admin,manager'
|
||||
*/
|
||||
public function getFiltersForRoute(string $search, ?string $verb = null): array;
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* 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 CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\Exceptions\BadRequestException;
|
||||
use CodeIgniter\HTTP\Exceptions\RedirectException;
|
||||
use CodeIgniter\HTTP\Method;
|
||||
use CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Router\Exceptions\RouterException;
|
||||
use Config\App;
|
||||
use Config\Feature;
|
||||
use Config\Routing;
|
||||
|
||||
/**
|
||||
* Request router.
|
||||
*
|
||||
* @see \CodeIgniter\Router\RouterTest
|
||||
*/
|
||||
class Router implements RouterInterface
|
||||
{
|
||||
/**
|
||||
* List of allowed HTTP methods (and CLI for command line use).
|
||||
*/
|
||||
public const HTTP_METHODS = [
|
||||
Method::GET,
|
||||
Method::HEAD,
|
||||
Method::POST,
|
||||
Method::PATCH,
|
||||
Method::PUT,
|
||||
Method::DELETE,
|
||||
Method::OPTIONS,
|
||||
Method::TRACE,
|
||||
Method::CONNECT,
|
||||
'CLI',
|
||||
];
|
||||
|
||||
/**
|
||||
* A RouteCollection instance.
|
||||
*
|
||||
* @var RouteCollectionInterface
|
||||
*/
|
||||
protected $collection;
|
||||
|
||||
/**
|
||||
* Sub-directory that contains the requested controller class.
|
||||
* Primarily used by 'autoRoute'.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* The name of the controller class.
|
||||
*
|
||||
* @var (Closure(mixed...): (ResponseInterface|string|void))|string
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* The name of the method to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $method;
|
||||
|
||||
/**
|
||||
* An array of binds that were collected
|
||||
* so they can be sent to closure routes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $params = [];
|
||||
|
||||
/**
|
||||
* The name of the front controller.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $indexPage = 'index.php';
|
||||
|
||||
/**
|
||||
* Whether dashes in URI's should be converted
|
||||
* to underscores when determining method names.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $translateURIDashes = false;
|
||||
|
||||
/**
|
||||
* The route that was matched for this request.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $matchedRoute;
|
||||
|
||||
/**
|
||||
* The options set for the matched route.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $matchedRouteOptions;
|
||||
|
||||
/**
|
||||
* The locale that was detected in a route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $detectedLocale;
|
||||
|
||||
/**
|
||||
* The filter info from Route Collection
|
||||
* if the matched route should be filtered.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $filtersInfo = [];
|
||||
|
||||
protected ?AutoRouterInterface $autoRouter = null;
|
||||
|
||||
/**
|
||||
* Permitted URI chars
|
||||
*
|
||||
* The default value is `''` (do not check) for backward compatibility.
|
||||
*/
|
||||
protected string $permittedURIChars = '';
|
||||
|
||||
/**
|
||||
* Stores a reference to the RouteCollection object.
|
||||
*/
|
||||
public function __construct(RouteCollectionInterface $routes, ?Request $request = null)
|
||||
{
|
||||
$config = config(App::class);
|
||||
|
||||
if (isset($config->permittedURIChars)) {
|
||||
$this->permittedURIChars = $config->permittedURIChars;
|
||||
}
|
||||
|
||||
$this->collection = $routes;
|
||||
|
||||
// These are only for auto-routing
|
||||
$this->controller = $this->collection->getDefaultController();
|
||||
$this->method = $this->collection->getDefaultMethod();
|
||||
|
||||
$this->collection->setHTTPVerb($request->getMethod() === '' ? $_SERVER['REQUEST_METHOD'] : $request->getMethod());
|
||||
|
||||
$this->translateURIDashes = $this->collection->shouldTranslateURIDashes();
|
||||
|
||||
if ($this->collection->shouldAutoRoute()) {
|
||||
$autoRoutesImproved = config(Feature::class)->autoRoutesImproved ?? false;
|
||||
if ($autoRoutesImproved) {
|
||||
assert($this->collection instanceof RouteCollection);
|
||||
|
||||
$this->autoRouter = new AutoRouterImproved(
|
||||
$this->collection->getRegisteredControllers('*'),
|
||||
$this->collection->getDefaultNamespace(),
|
||||
$this->collection->getDefaultController(),
|
||||
$this->collection->getDefaultMethod(),
|
||||
$this->translateURIDashes,
|
||||
);
|
||||
} else {
|
||||
$this->autoRouter = new AutoRouter(
|
||||
$this->collection->getRoutes('CLI', false),
|
||||
$this->collection->getDefaultNamespace(),
|
||||
$this->collection->getDefaultController(),
|
||||
$this->collection->getDefaultMethod(),
|
||||
$this->translateURIDashes,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the controller corresponding to the URI.
|
||||
*
|
||||
* @param string|null $uri URI path relative to baseURL
|
||||
*
|
||||
* @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
|
||||
*
|
||||
* @throws BadRequestException
|
||||
* @throws PageNotFoundException
|
||||
* @throws RedirectException
|
||||
*/
|
||||
public function handle(?string $uri = null)
|
||||
{
|
||||
// If we cannot find a URI to match against, then set it to root (`/`).
|
||||
if ($uri === null || $uri === '') {
|
||||
$uri = '/';
|
||||
}
|
||||
|
||||
// Decode URL-encoded string
|
||||
$uri = urldecode($uri);
|
||||
|
||||
$this->checkDisallowedChars($uri);
|
||||
|
||||
// Restart filterInfo
|
||||
$this->filtersInfo = [];
|
||||
|
||||
// Checks defined routes
|
||||
if ($this->checkRoutes($uri)) {
|
||||
if ($this->collection->isFiltered($this->matchedRoute[0])) {
|
||||
$this->filtersInfo = $this->collection->getFiltersForRoute($this->matchedRoute[0]);
|
||||
}
|
||||
|
||||
return $this->controller;
|
||||
}
|
||||
|
||||
// Still here? Then we can try to match the URI against
|
||||
// Controllers/directories, but the application may not
|
||||
// want this, like in the case of API's.
|
||||
if (! $this->collection->shouldAutoRoute()) {
|
||||
throw new PageNotFoundException(
|
||||
"Can't find a route for '{$this->collection->getHTTPVerb()}: {$uri}'.",
|
||||
);
|
||||
}
|
||||
|
||||
// Checks auto routes
|
||||
$this->autoRoute($uri);
|
||||
|
||||
return $this->controllerName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter info for the matched route, if any.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return $this->filtersInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the matched controller or closure.
|
||||
*
|
||||
* @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
|
||||
*/
|
||||
public function controllerName()
|
||||
{
|
||||
return $this->translateURIDashes && ! $this->controller instanceof Closure
|
||||
? str_replace('-', '_', $this->controller)
|
||||
: $this->controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the method to run in the
|
||||
* chosen controller.
|
||||
*/
|
||||
public function methodName(): string
|
||||
{
|
||||
return $this->translateURIDashes
|
||||
? str_replace('-', '_', $this->method)
|
||||
: $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 404 Override settings from the Collection.
|
||||
* If the override is a string, will split to controller/index array.
|
||||
*
|
||||
* @return array{string, string}|(Closure(string): (ResponseInterface|string|void))|null
|
||||
*/
|
||||
public function get404Override()
|
||||
{
|
||||
$route = $this->collection->get404Override();
|
||||
|
||||
if (is_string($route)) {
|
||||
$routeArray = explode('::', $route);
|
||||
|
||||
return [
|
||||
$routeArray[0], // Controller
|
||||
$routeArray[1] ?? 'index', // Method
|
||||
];
|
||||
}
|
||||
|
||||
if (is_callable($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binds that have been matched and collected
|
||||
* during the parsing process as an array, ready to send to
|
||||
* instance->method(...$params).
|
||||
*/
|
||||
public function params(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the sub-directory the controller is in,
|
||||
* if any. Relative to APPPATH.'Controllers'.
|
||||
*
|
||||
* Only used when auto-routing is turned on.
|
||||
*/
|
||||
public function directory(): string
|
||||
{
|
||||
if ($this->autoRouter instanceof AutoRouter) {
|
||||
return $this->autoRouter->directory();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the routing information that was matched for this
|
||||
* request, if a route was defined.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getMatchedRoute()
|
||||
{
|
||||
return $this->matchedRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all options set for the matched route
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getMatchedRouteOptions()
|
||||
{
|
||||
return $this->matchedRouteOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 you are using
|
||||
* something like mod_rewrite to remove the page. This allows you to set
|
||||
* it a blank.
|
||||
*
|
||||
* @param string $page
|
||||
*/
|
||||
public function setIndexPage($page): self
|
||||
{
|
||||
$this->indexPage = $page;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the system whether we should translate URI dashes or not
|
||||
* in the URI from a dash to an underscore.
|
||||
*
|
||||
* @deprecated This method should be removed.
|
||||
*/
|
||||
public function setTranslateURIDashes(bool $val = false): self
|
||||
{
|
||||
if ($this->autoRouter instanceof AutoRouter) {
|
||||
$this->autoRouter->setTranslateURIDashes($val);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true/false based on whether the current route contained
|
||||
* a {locale} placeholder.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLocale()
|
||||
{
|
||||
return (bool) $this->detectedLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detected locale, if any, or null.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocale()
|
||||
{
|
||||
return $this->detectedLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks Defined Routes.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param string $uri The URI path to compare against the routes
|
||||
*
|
||||
* @return bool Whether the route was matched or not.
|
||||
*
|
||||
* @throws RedirectException
|
||||
*/
|
||||
protected function checkRoutes(string $uri): bool
|
||||
{
|
||||
$routes = $this->collection->getRoutes($this->collection->getHTTPVerb());
|
||||
|
||||
// Don't waste any time
|
||||
if ($routes === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$uri = $uri === '/'
|
||||
? $uri
|
||||
: trim($uri, '/ ');
|
||||
|
||||
// Loop through the route array looking for wildcards
|
||||
foreach ($routes as $routeKey => $handler) {
|
||||
$routeKey = $routeKey === '/'
|
||||
? $routeKey
|
||||
// $routeKey may be int, because it is an array key,
|
||||
// and the URI `/1` is valid. The leading `/` is removed.
|
||||
: ltrim((string) $routeKey, '/ ');
|
||||
|
||||
$matchedKey = $routeKey;
|
||||
|
||||
// Are we dealing with a locale?
|
||||
if (str_contains($routeKey, '{locale}')) {
|
||||
$routeKey = str_replace('{locale}', '[^/]+', $routeKey);
|
||||
}
|
||||
|
||||
// Does the RegEx match?
|
||||
if (preg_match('#^' . $routeKey . '$#u', $uri, $matches)) {
|
||||
// Is this route supposed to redirect to another?
|
||||
if ($this->collection->isRedirect($routeKey)) {
|
||||
// replacing matched route groups with references: post/([0-9]+) -> post/$1
|
||||
$redirectTo = preg_replace_callback('/(\([^\(]+\))/', static function (): string {
|
||||
static $i = 1;
|
||||
|
||||
return '$' . $i++;
|
||||
}, is_array($handler) ? key($handler) : $handler);
|
||||
|
||||
throw new RedirectException(
|
||||
preg_replace('#\A' . $routeKey . '\z#u', $redirectTo, $uri),
|
||||
$this->collection->getRedirectCode($routeKey),
|
||||
);
|
||||
}
|
||||
// Store our locale so CodeIgniter object can
|
||||
// assign it to the Request.
|
||||
if (str_contains($matchedKey, '{locale}')) {
|
||||
preg_match(
|
||||
'#^' . str_replace('{locale}', '(?<locale>[^/]+)', $matchedKey) . '$#u',
|
||||
$uri,
|
||||
$matched,
|
||||
);
|
||||
|
||||
if ($this->collection->shouldUseSupportedLocalesOnly()
|
||||
&& ! in_array($matched['locale'], config(App::class)->supportedLocales, true)) {
|
||||
// Throw exception to prevent the autorouter, if enabled,
|
||||
// from trying to find a route
|
||||
throw PageNotFoundException::forLocaleNotSupported($matched['locale']);
|
||||
}
|
||||
|
||||
$this->detectedLocale = $matched['locale'];
|
||||
unset($matched);
|
||||
}
|
||||
|
||||
// Are we using Closures? If so, then we need
|
||||
// to collect the params into an array
|
||||
// so it can be passed to the controller method later.
|
||||
if (! is_string($handler) && is_callable($handler)) {
|
||||
$this->controller = $handler;
|
||||
|
||||
// Remove the original string from the matches array
|
||||
array_shift($matches);
|
||||
|
||||
$this->params = $matches;
|
||||
|
||||
$this->setMatchedRoute($matchedKey, $handler);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($handler, '::')) {
|
||||
[$controller, $methodAndParams] = explode('::', $handler);
|
||||
} else {
|
||||
$controller = $handler;
|
||||
$methodAndParams = '';
|
||||
}
|
||||
|
||||
// Checks `/` in controller name
|
||||
if (str_contains($controller, '/')) {
|
||||
throw RouterException::forInvalidControllerName($handler);
|
||||
}
|
||||
|
||||
if (str_contains($handler, '$') && str_contains($routeKey, '(')) {
|
||||
// Checks dynamic controller
|
||||
if (str_contains($controller, '$')) {
|
||||
throw RouterException::forDynamicController($handler);
|
||||
}
|
||||
|
||||
if (config(Routing::class)->multipleSegmentsOneParam === false) {
|
||||
// Using back-references
|
||||
$segments = explode('/', preg_replace('#\A' . $routeKey . '\z#u', $handler, $uri));
|
||||
} else {
|
||||
if (str_contains($methodAndParams, '/')) {
|
||||
[$method, $handlerParams] = explode('/', $methodAndParams, 2);
|
||||
$params = explode('/', $handlerParams);
|
||||
$handlerSegments = array_merge([$controller . '::' . $method], $params);
|
||||
} else {
|
||||
$handlerSegments = [$handler];
|
||||
}
|
||||
|
||||
$segments = [];
|
||||
|
||||
foreach ($handlerSegments as $segment) {
|
||||
$segments[] = $this->replaceBackReferences($segment, $matches);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$segments = explode('/', $handler);
|
||||
}
|
||||
|
||||
$this->setRequest($segments);
|
||||
|
||||
$this->setMatchedRoute($matchedKey, $handler);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace string `$n` with `$matches[n]` value.
|
||||
*/
|
||||
private function replaceBackReferences(string $input, array $matches): string
|
||||
{
|
||||
$pattern = '/\$([1-' . count($matches) . '])/u';
|
||||
|
||||
return preg_replace_callback(
|
||||
$pattern,
|
||||
static function ($match) use ($matches) {
|
||||
$index = (int) $match[1];
|
||||
|
||||
return $matches[$index] ?? '';
|
||||
},
|
||||
$input,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks Auto Routes.
|
||||
*
|
||||
* Attempts to match a URI path against Controllers and directories
|
||||
* found in APPPATH/Controllers, to find a matching route.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function autoRoute(string $uri)
|
||||
{
|
||||
[$this->directory, $this->controller, $this->method, $this->params]
|
||||
= $this->autoRouter->getRoute($uri, $this->collection->getHTTPVerb());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @deprecated this function name does not properly describe its behavior so it has been deprecated
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function validateRequest(array $segments): array
|
||||
{
|
||||
return $this->scanControllers($segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @deprecated Not used. Moved to AutoRouter class.
|
||||
*/
|
||||
protected function scanControllers(array $segments): array
|
||||
{
|
||||
$segments = array_filter($segments, static fn ($segment): bool => $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @deprecated This method should be removed.
|
||||
*/
|
||||
public function setDirectory(?string $dir = null, bool $append = false, bool $validate = true)
|
||||
{
|
||||
if ($dir === null || $dir === '') {
|
||||
$this->directory = null;
|
||||
}
|
||||
|
||||
if ($this->autoRouter instanceof AutoRouter) {
|
||||
$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
|
||||
{
|
||||
return (bool) preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request route
|
||||
*
|
||||
* Takes an array of URI segments as input and sets the class/method
|
||||
* to be called.
|
||||
*
|
||||
* @param array $segments URI segments
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setRequest(array $segments = [])
|
||||
{
|
||||
// If we don't have any segments - use the default controller;
|
||||
if ($segments === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$controller, $method] = array_pad(explode('::', $segments[0]), 2, null);
|
||||
|
||||
$this->controller = $controller;
|
||||
|
||||
// $this->method already contains the default method name,
|
||||
// so don't overwrite it with emptiness.
|
||||
if (! empty($method)) {
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
array_shift($segments);
|
||||
|
||||
$this->params = $segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default controller based on the info set in the RouteCollection.
|
||||
*
|
||||
* @deprecated This was an unnecessary method, so it is no longer used.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setDefaultController()
|
||||
{
|
||||
if (empty($this->controller)) {
|
||||
throw RouterException::forMissingDefaultRoute();
|
||||
}
|
||||
|
||||
sscanf($this->controller, '%[^/]/%s', $class, $this->method);
|
||||
|
||||
if (! is_file(APPPATH . 'Controllers/' . $this->directory . ucfirst($class) . '.php')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->controller = ucfirst($class);
|
||||
|
||||
log_message('info', 'Used the default controller.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|string $handler
|
||||
*/
|
||||
protected function setMatchedRoute(string $route, $handler): void
|
||||
{
|
||||
$this->matchedRoute = [$route, $handler];
|
||||
|
||||
$this->matchedRouteOptions = $this->collection->getRoutesOptions($route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks disallowed characters
|
||||
*/
|
||||
private function checkDisallowedChars(string $uri): void
|
||||
{
|
||||
foreach (explode('/', $uri) as $segment) {
|
||||
if ($segment !== '' && $this->permittedURIChars !== ''
|
||||
&& preg_match('/\A[' . $this->permittedURIChars . ']+\z/iu', $segment) !== 1
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
'The URI you submitted has disallowed characters: "' . $segment . '"',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* 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 CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Expected behavior of a Router.
|
||||
*/
|
||||
interface RouterInterface
|
||||
{
|
||||
/**
|
||||
* Stores a reference to the RouteCollection object.
|
||||
*/
|
||||
public function __construct(RouteCollectionInterface $routes, ?Request $request = null);
|
||||
|
||||
/**
|
||||
* Finds the controller method corresponding to the URI.
|
||||
*
|
||||
* @param string|null $uri URI path relative to baseURL
|
||||
*
|
||||
* @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
|
||||
*/
|
||||
public function handle(?string $uri = null);
|
||||
|
||||
/**
|
||||
* Returns the name of the matched controller.
|
||||
*
|
||||
* @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
|
||||
*/
|
||||
public function controllerName();
|
||||
|
||||
/**
|
||||
* Returns the name of the method in the controller to run.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function methodName();
|
||||
|
||||
/**
|
||||
* Returns the binds that have been matched and collected
|
||||
* during the parsing process as an array, ready to send to
|
||||
* instance->method(...$params).
|
||||
*
|
||||
* @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 you are using
|
||||
* something like mod_rewrite to remove the page. This allows you to set
|
||||
* it a blank.
|
||||
*
|
||||
* @param string $page
|
||||
*
|
||||
* @return RouterInterface
|
||||
*/
|
||||
public function setIndexPage($page);
|
||||
}
|
||||
Reference in New Issue
Block a user