mirror of
https://github.com/codeigniter4/CodeIgniter4.git
synced 2025-02-20 11:44:28 +08:00
Merge pull request #5128 from kenjis/add-multiple-filters
Multiple filters for a route and classname filter
This commit is contained in:
commit
3ce7a52e77
27
app/Config/Feature.php
Normal file
27
app/Config/Feature.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
/**
|
||||
* Enable/disable backward compatibility breaking features.
|
||||
*/
|
||||
class Feature extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Enable multiple filters for a route or not
|
||||
*
|
||||
* If you enable this:
|
||||
* - CodeIgniter\CodeIgniter::handleRequest() uses:
|
||||
* - CodeIgniter\Filters\Filters::enableFilters(), instead of enableFilter()
|
||||
* - CodeIgniter\CodeIgniter::tryToRouteIt() uses:
|
||||
* - CodeIgniter\Router\Router::getFilters(), instead of getFilter()
|
||||
* - CodeIgniter\Router\Router::handle() uses:
|
||||
* - property $filtersInfo, instead of $filterInfo
|
||||
* - CodeIgniter\Router\RouteCollection::getFiltersForRoute(), instead of getFilterForRoute()
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $multipleFilters = false;
|
||||
}
|
@ -32,7 +32,7 @@ parameters:
|
||||
- '#Access to an undefined property CodeIgniter\\Database\\BaseConnection::\$mysqli|\$schema#'
|
||||
- '#Access to an undefined property CodeIgniter\\Database\\ConnectionInterface::(\$DBDriver|\$connID|\$likeEscapeStr|\$likeEscapeChar|\$escapeChar|\$protectIdentifiers|\$schema)#'
|
||||
- '#Call to an undefined method CodeIgniter\\Database\\BaseConnection::_(disable|enable)ForeignKeyChecks\(\)#'
|
||||
- '#Call to an undefined method CodeIgniter\\Router\\RouteCollectionInterface::(getDefaultNamespace|isFiltered|getFilterForRoute|getRoutesOptions)\(\)#'
|
||||
- '#Call to an undefined method CodeIgniter\\Router\\RouteCollectionInterface::(getDefaultNamespace|isFiltered|getFilterForRoute|getFiltersForRoute|getRoutesOptions)\(\)#'
|
||||
- '#Cannot access property [\$a-z_]+ on ((bool\|)?object\|resource)#'
|
||||
- '#Cannot call method [a-zA-Z_]+\(\) on ((bool\|)?object\|resource)#'
|
||||
- '#Method CodeIgniter\\Router\\RouteCollectionInterface::getRoutes\(\) invoked with 1 parameter, 0 required#'
|
||||
|
@ -364,8 +364,15 @@ class CodeIgniter
|
||||
// If any filters were specified within the routes file,
|
||||
// we need to ensure it's active for the current request
|
||||
if ($routeFilter !== null) {
|
||||
$filters->enableFilter($routeFilter, 'before');
|
||||
$filters->enableFilter($routeFilter, 'after');
|
||||
$multipleFiltersEnabled = config('Feature')->multipleFilters ?? false;
|
||||
if ($multipleFiltersEnabled) {
|
||||
$filters->enableFilters($routeFilter, 'before');
|
||||
$filters->enableFilters($routeFilter, 'after');
|
||||
} else {
|
||||
// for backward compatibility
|
||||
$filters->enableFilter($routeFilter, 'before');
|
||||
$filters->enableFilter($routeFilter, 'after');
|
||||
}
|
||||
}
|
||||
|
||||
$uri = $this->determinePath();
|
||||
@ -690,7 +697,7 @@ class CodeIgniter
|
||||
*
|
||||
* @throws RedirectException
|
||||
*
|
||||
* @return string|null
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
|
||||
{
|
||||
@ -719,7 +726,13 @@ class CodeIgniter
|
||||
|
||||
$this->benchmark->stop('routing');
|
||||
|
||||
return $this->router->getFilter();
|
||||
// for backward compatibility
|
||||
$multipleFiltersEnabled = config('Feature')->multipleFilters ?? false;
|
||||
if (! $multipleFiltersEnabled) {
|
||||
return $this->router->getFilter();
|
||||
}
|
||||
|
||||
return $this->router->getFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -319,6 +319,8 @@ class Filters
|
||||
* are passed to the filter when executed.
|
||||
*
|
||||
* @return Filters
|
||||
*
|
||||
* @deprecated Use enableFilters(). This method will be private.
|
||||
*/
|
||||
public function enableFilter(string $name, string $when = 'before')
|
||||
{
|
||||
@ -334,7 +336,9 @@ class Filters
|
||||
$this->arguments[$name] = $params;
|
||||
}
|
||||
|
||||
if (! array_key_exists($name, $this->config->aliases)) {
|
||||
if (class_exists($name)) {
|
||||
$this->config->aliases[$name] = $name;
|
||||
} elseif (! array_key_exists($name, $this->config->aliases)) {
|
||||
throw FilterException::forNoAlias($name);
|
||||
}
|
||||
|
||||
@ -352,6 +356,24 @@ class Filters
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that specific filters is on and enabled for the current request.
|
||||
*
|
||||
* Filters can have "arguments". This is done by placing a colon immediately
|
||||
* after the filter name, followed by a comma-separated list of arguments that
|
||||
* are passed to the filter when executed.
|
||||
*
|
||||
* @return Filters
|
||||
*/
|
||||
public function enableFilters(array $names, string $when = 'before')
|
||||
{
|
||||
foreach ($names as $filter) {
|
||||
$this->enableFilter($filter, $when);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arguments for a specified key, or all.
|
||||
*
|
||||
|
@ -512,7 +512,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
* Example:
|
||||
* $routes->add('news', 'Posts::index');
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function add(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -821,7 +821,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
* Example:
|
||||
* $route->match( ['get', 'post'], 'users/(:num)', 'users/$1);
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function match(array $verbs = [], string $from = '', $to = '', ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -841,7 +841,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to GET requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function get(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -853,7 +853,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to POST requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function post(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -865,7 +865,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to PUT requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function put(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -877,7 +877,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to DELETE requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function delete(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -889,7 +889,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to HEAD requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function head(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -901,7 +901,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to PATCH requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function patch(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -913,7 +913,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to OPTIONS requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function options(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -925,7 +925,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
/**
|
||||
* Specifies a route that is only available to command-line requests.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
public function cli(string $from, $to, ?array $options = null): RouteCollectionInterface
|
||||
{
|
||||
@ -1040,6 +1040,8 @@ class RouteCollection implements RouteCollectionInterface
|
||||
* 'role:admin,manager'
|
||||
*
|
||||
* has a filter of "role", with parameters of ['admin', 'manager'].
|
||||
*
|
||||
* @deprecated Use getFiltersForRoute()
|
||||
*/
|
||||
public function getFilterForRoute(string $search, ?string $verb = null): string
|
||||
{
|
||||
@ -1048,6 +1050,27 @@ class RouteCollection implements RouteCollectionInterface
|
||||
return $options[$search]['filter'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'].
|
||||
*/
|
||||
public function getFiltersForRoute(string $search, ?string $verb = null): array
|
||||
{
|
||||
$options = $this->loadRoutesOptions($verb);
|
||||
|
||||
if (is_string($options[$search]['filter'])) {
|
||||
return [$options[$search]['filter']];
|
||||
}
|
||||
|
||||
return $options[$search]['filter'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a
|
||||
*
|
||||
@ -1083,7 +1106,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
* the request method(s) that this route will work for. They can be separated
|
||||
* by a pipe character "|" if there is more than one.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array|Closure|string $to
|
||||
*/
|
||||
protected function create(string $verb, string $from, $to, ?array $options = null)
|
||||
{
|
||||
|
@ -28,8 +28,8 @@ interface RouteCollectionInterface
|
||||
/**
|
||||
* Adds a single route to the collection.
|
||||
*
|
||||
* @param array|string $to
|
||||
* @param array $options
|
||||
* @param array|Closure|string $to
|
||||
* @param array $options
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -99,9 +99,19 @@ class Router implements RouterInterface
|
||||
* if the matched route should be filtered.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated Use $filtersInfo
|
||||
*/
|
||||
protected $filterInfo;
|
||||
|
||||
/**
|
||||
* The filter info from Route Collection
|
||||
* if the matched route should be filtered.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $filtersInfo = [];
|
||||
|
||||
/**
|
||||
* Stores a reference to the RouteCollection object.
|
||||
*
|
||||
@ -144,7 +154,13 @@ class Router implements RouterInterface
|
||||
|
||||
if ($this->checkRoutes($uri)) {
|
||||
if ($this->collection->isFiltered($this->matchedRoute[0])) {
|
||||
$this->filterInfo = $this->collection->getFilterForRoute($this->matchedRoute[0]);
|
||||
$multipleFiltersEnabled = config('Feature')->multipleFilters ?? false;
|
||||
if ($multipleFiltersEnabled) {
|
||||
$this->filtersInfo = $this->collection->getFiltersForRoute($this->matchedRoute[0]);
|
||||
} else {
|
||||
// for backward compatibility
|
||||
$this->filterInfo = $this->collection->getFilterForRoute($this->matchedRoute[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->controller;
|
||||
@ -166,12 +182,24 @@ class Router implements RouterInterface
|
||||
* Returns the filter info for the matched route, if any.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated Use getFilters()
|
||||
*/
|
||||
public function getFilter()
|
||||
{
|
||||
return $this->filterInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter info for the matched route, if any.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return $this->filtersInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the matched controller.
|
||||
*
|
||||
|
@ -17,6 +17,7 @@ use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\Mock\MockCodeIgniter;
|
||||
use Config\App;
|
||||
use Config\Modules;
|
||||
use Tests\Support\Filters\Customfilter;
|
||||
|
||||
/**
|
||||
* @backupGlobals enabled
|
||||
@ -172,6 +173,29 @@ final class CodeIgniterTest extends CIUnitTestCase
|
||||
$this->assertStringContainsString("You want to see 'about' page.", $output);
|
||||
}
|
||||
|
||||
public function testControllersRunFilterByClassName()
|
||||
{
|
||||
$_SERVER['argv'] = ['index.php', 'pages/about'];
|
||||
$_SERVER['argc'] = 2;
|
||||
|
||||
$_SERVER['REQUEST_URI'] = '/pages/about';
|
||||
|
||||
// Inject mock router.
|
||||
$routes = Services::routes();
|
||||
$routes->add('pages/about', static function () {
|
||||
return Services::request()->url;
|
||||
}, ['filter' => Customfilter::class]);
|
||||
|
||||
$router = Services::router($routes, Services::request());
|
||||
Services::injectMock('router', $router);
|
||||
|
||||
ob_start();
|
||||
$this->codeigniter->useSafeOutput(true)->run();
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertStringContainsString('http://hellowworld.com', $output);
|
||||
}
|
||||
|
||||
public function testResponseConfigEmpty()
|
||||
{
|
||||
$_SERVER['argv'] = ['index.php', '/'];
|
||||
|
@ -16,6 +16,7 @@ use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\Modules;
|
||||
use Tests\Support\Filters\Customfilter;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -529,6 +530,39 @@ final class RouterTest extends CIUnitTestCase
|
||||
$this->assertSame('api-auth', $router->getFilter());
|
||||
}
|
||||
|
||||
public function testRouteWorksWithClassnameFilter()
|
||||
{
|
||||
$collection = $this->collection;
|
||||
|
||||
$collection->add('foo', 'TestController::foo', ['filter' => Customfilter::class]);
|
||||
$router = new Router($collection, $this->request);
|
||||
|
||||
$router->handle('foo');
|
||||
|
||||
$this->assertSame('\TestController', $router->controllerName());
|
||||
$this->assertSame('foo', $router->methodName());
|
||||
$this->assertSame('Tests\Support\Filters\Customfilter', $router->getFilter());
|
||||
}
|
||||
|
||||
public function testRouteWorksWithMultipleFilters()
|
||||
{
|
||||
$feature = config('Feature');
|
||||
$feature->multipleFilters = true;
|
||||
|
||||
$collection = $this->collection;
|
||||
|
||||
$collection->add('foo', 'TestController::foo', ['filter' => ['filter1', 'filter2:param']]);
|
||||
$router = new Router($collection, $this->request);
|
||||
|
||||
$router->handle('foo');
|
||||
|
||||
$this->assertSame('\TestController', $router->controllerName());
|
||||
$this->assertSame('foo', $router->methodName());
|
||||
$this->assertSame(['filter1', 'filter2:param'], $router->getFilters());
|
||||
|
||||
$feature->multipleFilters = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1240
|
||||
*/
|
||||
|
@ -352,15 +352,37 @@ can modify the generated routes, or further restrict them. The ``$options`` arra
|
||||
Applying Filters
|
||||
----------------
|
||||
|
||||
You can alter the behavior of specific routes by supplying a filter to run before or after the controller. This is especially handy during authentication or api logging::
|
||||
You can alter the behavior of specific routes by supplying filters to run before or after the controller. This is especially handy during authentication or api logging.
|
||||
The value for the filter can be a string or an array of strings:
|
||||
|
||||
* matching the aliases defined in ``app/Config/Filters.php``.
|
||||
* filter classnames
|
||||
|
||||
See `Controller filters <filters.html>`_ for more information on setting up filters.
|
||||
|
||||
**Alias filter**
|
||||
|
||||
You specify an alias defined in ``app/Config/Filters.php`` for the filter value::
|
||||
|
||||
$routes->add('admin',' AdminController::index', ['filter' => 'admin-auth']);
|
||||
|
||||
The value for the filter must match one of the aliases defined within ``app/Config/Filters.php``. You may also supply arguments to be passed to the filter's ``before()`` and ``after()`` methods::
|
||||
You may also supply arguments to be passed to the alias filter's ``before()`` and ``after()`` methods::
|
||||
|
||||
$routes->add('users/delete/(:segment)', 'AdminController::index', ['filter' => 'admin-auth:dual,noreturn']);
|
||||
|
||||
See `Controller filters <filters.html>`_ for more information on setting up filters.
|
||||
**Classname filter**
|
||||
|
||||
You specify a filter classname for the filter value::
|
||||
|
||||
$routes->add('admin',' AdminController::index', ['filter' => \App\Filters\SomeFilter::class]);
|
||||
|
||||
**Multiple filters**
|
||||
|
||||
.. important:: *Multiple filters* is disabled by default. Because it breaks backward compatibility. If you want to use it, you need to configure. See *Multiple filters for a route* in :doc:`/installation/upgrade_415` for the details.
|
||||
|
||||
You specify an array for the filter value::
|
||||
|
||||
$routes->add('admin',' AdminController::index', ['filter' => ['admin-auth', \App\Filters\SomeFilter::class]]);
|
||||
|
||||
Assigning Namespace
|
||||
-------------------
|
||||
|
@ -25,3 +25,31 @@ Update the definition of the session table. See the :doc:`/libraries/sessions` f
|
||||
|
||||
The change was introduced in v4.1.2. But due to `a bug <https://github.com/codeigniter4/CodeIgniter4/issues/4807>`_,
|
||||
the DatabaseHandler Driver did not work properly.
|
||||
|
||||
**Multiple filters for a route**
|
||||
|
||||
A new feature to set multiple filters for a route.
|
||||
|
||||
.. important:: This feature is disabled by default. Because it breaks backward compatibility.
|
||||
|
||||
If you want to use this, you need to set the property ``$multipleFilters`` ``true`` in ``app/Config/Feature.php``.
|
||||
If you enable it:
|
||||
|
||||
- ``CodeIgniter\CodeIgniter::handleRequest()`` uses
|
||||
- ``CodeIgniter\Filters\Filters::enableFilters()``, instead of ``enableFilter()``
|
||||
- ``CodeIgniter\CodeIgniter::tryToRouteIt()`` uses
|
||||
- ``CodeIgniter\Router\Router::getFilters()``, instead of ``getFilter()``
|
||||
- ``CodeIgniter\Router\Router::handle()`` uses
|
||||
- the property ``$filtersInfo``, instead of ``$filterInfo``
|
||||
- ``CodeIgniter\Router\RouteCollection::getFiltersForRoute()``, instead of ``getFilterForRoute()``
|
||||
|
||||
If you extended the above classes, then you need to change them.
|
||||
|
||||
The following methods and a property have been deprecated:
|
||||
|
||||
- ``CodeIgniter\Filters\Filters::enableFilter()``
|
||||
- ``CodeIgniter\Router\Router::getFilter()``
|
||||
- ``CodeIgniter\Router\RouteCollection::getFilterForRoute()``
|
||||
- ``CodeIgniter\Router\RouteCollection``'s property ``$filterInfo``
|
||||
|
||||
See *Applying Filters* in :doc:`Routing </incoming/routing>` for the functionality.
|
||||
|
Loading…
x
Reference in New Issue
Block a user