diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 05825b1d76ed..d55b67243b52 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -5323,18 +5323,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/Filters/Filters.php', ]; -$ignoreErrors[] = [ - // identifier: missingType.iterableValue - 'message' => '#^Method CodeIgniter\\\\Filters\\\\Filters\\:\\:getFilters\\(\\) return type has no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Filters/Filters.php', -]; -$ignoreErrors[] = [ - // identifier: missingType.iterableValue - 'message' => '#^Method CodeIgniter\\\\Filters\\\\Filters\\:\\:getFiltersClass\\(\\) return type has no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Filters/Filters.php', -]; $ignoreErrors[] = [ // identifier: missingType.iterableValue 'message' => '#^Method CodeIgniter\\\\Filters\\\\Filters\\:\\:getRequiredFilters\\(\\) return type has no value type specified in iterable type array\\.$#', @@ -5347,24 +5335,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/Filters/Filters.php', ]; -$ignoreErrors[] = [ - // identifier: missingType.iterableValue - 'message' => '#^Method CodeIgniter\\\\Filters\\\\Filters\\:\\:registerArguments\\(\\) has parameter \\$arguments with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Filters/Filters.php', -]; -$ignoreErrors[] = [ - // identifier: missingType.iterableValue - 'message' => '#^Method CodeIgniter\\\\Filters\\\\Filters\\:\\:runAfter\\(\\) has parameter \\$filterClasses with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Filters/Filters.php', -]; -$ignoreErrors[] = [ - // identifier: missingType.iterableValue - 'message' => '#^Method CodeIgniter\\\\Filters\\\\Filters\\:\\:runBefore\\(\\) has parameter \\$filterClasses with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Filters/Filters.php', -]; $ignoreErrors[] = [ // identifier: missingType.iterableValue 'message' => '#^Method CodeIgniter\\\\Filters\\\\Filters\\:\\:setToolbarToLast\\(\\) return type has no value type specified in iterable type array\\.$#', @@ -5377,18 +5347,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/Filters/Filters.php', ]; -$ignoreErrors[] = [ - // identifier: missingType.iterableValue - 'message' => '#^Property CodeIgniter\\\\Filters\\\\Filters\\:\\:\\$filters type has no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Filters/Filters.php', -]; -$ignoreErrors[] = [ - // identifier: missingType.iterableValue - 'message' => '#^Property CodeIgniter\\\\Filters\\\\Filters\\:\\:\\$filtersClass type has no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Filters/Filters.php', -]; $ignoreErrors[] = [ // identifier: missingType.iterableValue 'message' => '#^Method CodeIgniter\\\\Filters\\\\ForceHTTPS\\:\\:after\\(\\) has parameter \\$arguments with no value type specified in iterable type array\\.$#', @@ -14014,7 +13972,7 @@ $ignoreErrors[] = [ // identifier: codeigniter.superglobalAccessAssign 'message' => '#^Assigning \'GET\' directly on offset \'REQUEST_METHOD\' of \\$_SERVER is discouraged\\.$#', - 'count' => 36, + 'count' => 38, 'path' => __DIR__ . '/tests/system/Filters/FiltersTest.php', ]; $ignoreErrors[] = [ diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index d8e22835e2ed..972257ac69ab 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -14,7 +14,6 @@ namespace CodeIgniter\Filters; use CodeIgniter\Config\Filters as BaseFiltersConfig; -use CodeIgniter\Exceptions\ConfigException; use CodeIgniter\Filters\Exceptions\FilterException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -66,12 +65,28 @@ class Filters protected $initialized = false; /** - * The processed filters that will - * be used to check against. + * The filter list to execute for the current request (URI path). * + * This property is for display. Use $filtersClass to execute filters. * This does not include "Required Filters". * - * @var array + * [ + * 'before' => [ + * 'alias', + * 'alias:arg1', + * 'alias:arg1,arg2', + * ], + * 'after' => [ + * 'alias', + * 'alias:arg1', + * 'alias:arg1,arg2', + * ], + * ] + * + * @var array{ + * before: list, + * after: list + * } */ protected $filters = [ 'before' => [], @@ -79,22 +94,43 @@ class Filters ]; /** - * The collection of filters' class names that will - * be used to execute in each position. + * The collection of filter class names and its arguments to execute for the + * current request (URI path). * * This does not include "Required Filters". * - * @var array + * [ + * 'before' => [ + * [classname, arguments], + * ], + * 'after' => [ + * [classname, arguments], + * ], + * ] + * + * @var array{ + * before: list}>, + * after: list}> + * } */ protected $filtersClass = [ 'before' => [], 'after' => [], ]; + /** + * List of filter class instances. + * + * @var array [classname => instance] + */ + protected array $filterClassInstances = []; + /** * Any arguments to be passed to filters. * * @var array|null> [name => params] + * + * @deprecated 4.6.0 No longer used. */ protected $arguments = []; @@ -102,6 +138,8 @@ class Filters * Any arguments to be passed to filtersClass. * * @var array|null> [classname => arguments] + * + * @deprecated 4.6.0 No longer used. */ protected $argumentsClass = []; @@ -189,21 +227,19 @@ public function run(string $uri, string $position = 'before') } /** + * @param list}> $filterClassList [[classname, arguments], ...] + * * @return RequestInterface|ResponseInterface|string */ - private function runBefore(array $filterClasses) + private function runBefore(array $filterClassList) { - foreach ($filterClasses as $className) { - $class = new $className(); + foreach ($filterClassList as $filterClassInfo) { + $className = $filterClassInfo[0]; + $arguments = ($filterClassInfo[1] === []) ? null : $filterClassInfo[1]; - if (! $class instanceof FilterInterface) { - throw FilterException::forIncorrectInterface($class::class); - } + $instance = $this->createFilter($className); - $result = $class->before( - $this->request, - $this->argumentsClass[$className] ?? null - ); + $result = $instance->before($this->request, $arguments); if ($result instanceof RequestInterface) { $this->request = $result; @@ -229,20 +265,18 @@ private function runBefore(array $filterClasses) return $this->request; } - private function runAfter(array $filterClasses): ResponseInterface + /** + * @param list}> $filterClassList [[classname, arguments], ...] + */ + private function runAfter(array $filterClassList): ResponseInterface { - foreach ($filterClasses as $className) { - $class = new $className(); + foreach ($filterClassList as $filterClassInfo) { + $className = $filterClassInfo[0]; + $arguments = ($filterClassInfo[1] === []) ? null : $filterClassInfo[1]; - if (! $class instanceof FilterInterface) { - throw FilterException::forIncorrectInterface($class::class); - } + $instance = $this->createFilter($className); - $result = $class->after( - $this->request, - $this->response, - $this->argumentsClass[$className] ?? null - ); + $result = $instance->after($this->request, $this->response, $arguments); if ($result instanceof ResponseInterface) { $this->response = $result; @@ -254,6 +288,26 @@ private function runAfter(array $filterClasses): ResponseInterface return $this->response; } + /** + * @param class-string $className + */ + private function createFilter(string $className): FilterInterface + { + if (isset($this->filterClassInstances[$className])) { + return $this->filterClassInstances[$className]; + } + + $instance = new $className(); + + if (! $instance instanceof FilterInterface) { + throw FilterException::forIncorrectInterface($instance::class); + } + + $this->filterClassInstances[$className] = $instance; + + return $instance; + } + /** * Runs "Required Filters" for the specified position. * @@ -277,9 +331,11 @@ public function runRequired(string $position = 'before') foreach ($filters as $alias) { if (is_array($aliases[$alias])) { - $filterClasses[$position] = array_merge($filterClasses[$position], $aliases[$alias]); + foreach ($this->config->aliases[$alias] as $class) { + $filterClasses[$position][] = [$class, []]; + } } else { - $filterClasses[$position][] = $aliases[$alias]; + $filterClasses[$position][] = [$aliases[$alias], []]; } } @@ -379,6 +435,9 @@ private function setToolbarToLast(array $filters, bool $remove = false): array * @TODO We don't need to accept null as $uri. * * @return Filters + * + * @testTag Only for test code. The run() calls this, so you don't need to + * call this in your app. */ public function initialize(?string $uri = null) { @@ -403,6 +462,11 @@ public function initialize(?string $uri = null) // Set the toolbar filter to the last position to be executed $this->filters['after'] = $this->setToolbarToLast($this->filters['after']); + // Since some filters like rate limiters rely on being executed once a request, + // we filter em here. + $this->filters['before'] = array_unique($this->filters['before']); + $this->filters['after'] = array_unique($this->filters['after']); + $this->processAliasesToClass('before'); $this->processAliasesToClass('after'); @@ -433,6 +497,11 @@ public function reset(): self /** * Returns the processed filters array. * This does not include "Required Filters". + * + * @return array{ + * before: list, + * after: list + * } */ public function getFilters(): array { @@ -442,6 +511,11 @@ public function getFilters(): array /** * Returns the filtersClass array. * This does not include "Required Filters". + * + * @return array{ + * before: list}>, + * after: list}> + * } */ public function getFiltersClass(): array { @@ -482,29 +556,27 @@ public function addFilter(string $class, ?string $alias = null, string $when = ' * are passed to the filter when executed. * * @param string $name filter_name or filter_name:arguments like 'role:admin,manager' + * or filter classname. */ private function enableFilter(string $name, string $when = 'before'): void { - // Get arguments and clean name - [$name, $arguments] = $this->getCleanName($name); - $this->arguments[$name] = ($arguments !== []) ? $arguments : null; + // Normalize the arguments. + [$alias, $arguments] = $this->getCleanName($name); + $filter = ($arguments === []) ? $alias : $alias . ':' . implode(',', $arguments); - if (class_exists($name)) { - $this->config->aliases[$name] = $name; - } elseif (! array_key_exists($name, $this->config->aliases)) { - throw FilterException::forNoAlias($name); + if (class_exists($alias)) { + $this->config->aliases[$alias] = $alias; + } elseif (! array_key_exists($alias, $this->config->aliases)) { + throw FilterException::forNoAlias($alias); } - $classNames = (array) $this->config->aliases[$name]; - - foreach ($classNames as $className) { - $this->argumentsClass[$className] = $this->arguments[$name] ?? null; + if (! isset($this->filters[$when][$filter])) { + $this->filters[$when][] = $filter; } - if (! isset($this->filters[$when][$name])) { - $this->filters[$when][] = $name; - $this->filtersClass[$when] = array_merge($this->filtersClass[$when], $classNames); - } + // Since some filters like rate limiters rely on being executed once a request, + // we filter em here. + $this->filters[$when] = array_unique($this->filters[$when]); } /** @@ -554,6 +626,8 @@ public function enableFilters(array $names, string $when = 'before') * Returns the arguments for a specified key, or all. * * @return array|string + * + * @deprecated 4.6.0 Already does not work. */ public function getArguments(?string $key = null) { @@ -689,12 +763,7 @@ protected function processFilters(?string $uri = null) $path = $settings['before']; if ($this->pathApplies($uri, $path)) { - // Get arguments and clean name - [$name, $arguments] = $this->getCleanName($alias); - - $filters['before'][] = $name; - - $this->registerArguments($name, $arguments); + $filters['before'][] = $alias; } } @@ -702,14 +771,7 @@ protected function processFilters(?string $uri = null) $path = $settings['after']; if ($this->pathApplies($uri, $path)) { - // Get arguments and clean name - [$name, $arguments] = $this->getCleanName($alias); - - $filters['after'][] = $name; - - // The arguments may have already been registered in the before filter. - // So disable check. - $this->registerArguments($name, $arguments, false); + $filters['after'][] = $alias; } } } @@ -733,31 +795,6 @@ protected function processFilters(?string $uri = null) } } - /** - * @param string $name filter alias - * @param array $arguments filter arguments - * @param bool $check if true, check if already defined - */ - private function registerArguments(string $name, array $arguments, bool $check = true): void - { - if ($arguments !== []) { - if ($check && array_key_exists($name, $this->arguments)) { - throw new ConfigException( - '"' . $name . '" already has arguments: ' - . (($this->arguments[$name] === null) ? 'null' : implode(',', $this->arguments[$name])) - ); - } - - $this->arguments[$name] = $arguments; - } - - $classNames = (array) $this->config->aliases[$name]; - - foreach ($classNames as $className) { - $this->argumentsClass[$className] = $this->arguments[$name] ?? null; - } - } - /** * Maps filter aliases to the equivalent filter classes * @@ -769,32 +806,28 @@ protected function processAliasesToClass(string $position) { $filterClasses = []; - foreach ($this->filters[$position] as $alias => $rules) { - if (is_numeric($alias) && is_string($rules)) { - $alias = $rules; - } + foreach ($this->filters[$position] as $filter) { + // Get arguments and clean alias + [$alias, $arguments] = $this->getCleanName($filter); if (! array_key_exists($alias, $this->config->aliases)) { throw FilterException::forNoAlias($alias); } if (is_array($this->config->aliases[$alias])) { - $filterClasses = [...$filterClasses, ...$this->config->aliases[$alias]]; + foreach ($this->config->aliases[$alias] as $class) { + $filterClasses[] = [$class, $arguments]; + } } else { - $filterClasses[] = $this->config->aliases[$alias]; + $filterClasses[] = [$this->config->aliases[$alias], $arguments]; } } - // when using enableFilter() we already write the class name in $filterClasses as well as the - // alias in $filters. This leads to duplicates when using route filters. if ($position === 'before') { $this->filtersClass[$position] = array_merge($filterClasses, $this->filtersClass[$position]); } else { $this->filtersClass[$position] = array_merge($this->filtersClass[$position], $filterClasses); } - - // Since some filters like rate limiters rely on being executed once a request we filter em here. - $this->filtersClass[$position] = array_values(array_unique($this->filtersClass[$position])); } /** diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index a550e098e9b0..e65ba03c25c0 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -16,7 +16,6 @@ use App\Controllers\Home; use CodeIgniter\Config\Services; use CodeIgniter\Debug\Timer; -use CodeIgniter\Exceptions\ConfigException; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\Method; use CodeIgniter\HTTP\Response; @@ -313,9 +312,6 @@ public function testRunExecuteFilterByClassName(): void public function testRegisterSameFilterTwiceWithDifferentArgument(): void { - $this->expectException(ConfigException::class); - $this->expectExceptionMessage('"test-customfilter" already has arguments: null'); - $_SERVER['argv'] = ['index.php', 'pages/about']; $_SERVER['argc'] = 2; @@ -343,7 +339,11 @@ public function testRegisterSameFilterTwiceWithDifferentArgument(): void ]; Services::filters($filterConfig); + ob_start(); $this->codeigniter->run(); + $output = ob_get_clean(); + + $this->assertStringContainsString('http://hellowworld.comhttp://hellowworld.com', $output); $this->resetServices(); } diff --git a/tests/system/Filters/FiltersTest.php b/tests/system/Filters/FiltersTest.php index 3a35920989e5..003e6f7c5cbf 100644 --- a/tests/system/Filters/FiltersTest.php +++ b/tests/system/Filters/FiltersTest.php @@ -14,7 +14,6 @@ namespace CodeIgniter\Filters; use CodeIgniter\Config\Services; -use CodeIgniter\Exceptions\ConfigException; use CodeIgniter\Filters\Exceptions\FilterException; use CodeIgniter\Filters\fixtures\GoogleCurious; use CodeIgniter\Filters\fixtures\GoogleEmpty; @@ -75,7 +74,7 @@ protected function setUp(): void private function createFilters(FiltersConfig $config, $request = null): Filters { - $request ??= Services::request(); + $request ??= Services::incomingrequest(); return new Filters($config, $request, $this->response); } @@ -834,8 +833,8 @@ public function testEnableFilter(): void $filtersConfig = $this->createConfigFromArray(FiltersConfig::class, $config); $filters = $this->createFilters($filtersConfig); - $filters = $filters->initialize('admin/foo/bar'); $filters->enableFilters(['google'], 'before'); + $filters = $filters->initialize('admin/foo/bar'); $filters = $filters->getFilters(); $this->assertContains('google', $filters['before']); @@ -862,9 +861,7 @@ public function testFiltersWithArguments(): void $filters = $filters->initialize('admin/foo/bar'); $found = $filters->getFilters(); - $this->assertContains('role', $found['before']); - $this->assertSame(['admin', 'super'], $filters->getArguments('role')); - $this->assertSame(['role' => ['admin', 'super']], $filters->getArguments()); + $this->assertContains('role:admin,super', $found['before']); $response = $filters->run('admin/foo/bar', 'before'); @@ -875,11 +872,8 @@ public function testFiltersWithArguments(): void $this->assertSame('admin;super', $response->getBody()); } - public function testFilterWithArgumentsIsDefined(): void + public function testFilterWithDiffernetArguments(): void { - $this->expectException(ConfigException::class); - $this->expectExceptionMessage('"role" already has arguments: admin,super'); - $_SERVER['REQUEST_METHOD'] = 'GET'; $config = [ @@ -898,6 +892,10 @@ public function testFilterWithArgumentsIsDefined(): void $filters = $this->createFilters($filtersConfig); $filters->initialize('admin/user/bar'); + $found = $filters->getFilters(); + + $this->assertContains('role:admin,super', $found['before']); + $this->assertContains('role:super', $found['before']); } public function testFilterWithoutArgumentsIsDefined(): void @@ -923,8 +921,6 @@ public function testFilterWithoutArgumentsIsDefined(): void $found = $filters->getFilters(); $this->assertContains('role', $found['before']); - $this->assertSame(['super'], $filters->getArguments('role')); - $this->assertSame(['role' => ['super']], $filters->getArguments()); } public function testEnableFilterWithArguments(): void @@ -941,14 +937,11 @@ public function testEnableFilterWithArguments(): void $filtersConfig = $this->createConfigFromArray(FiltersConfig::class, $config); $filters = $this->createFilters($filtersConfig); - $filters = $filters->initialize('admin/foo/bar'); $filters->enableFilters(['role:admin , super'], 'before'); $filters->enableFilters(['role:admin , super'], 'after'); $found = $filters->getFilters(); - $this->assertContains('role', $found['before']); - $this->assertSame(['admin', 'super'], $filters->getArguments('role')); - $this->assertSame(['role' => ['admin', 'super']], $filters->getArguments()); + $this->assertContains('role:admin,super', $found['before']); $response = $filters->run('admin/foo/bar', 'before'); @@ -973,7 +966,6 @@ public function testEnableFilterWithNoArguments(): void $filtersConfig = $this->createConfigFromArray(FiltersConfig::class, $config); $filters = $this->createFilters($filtersConfig); - $filters = $filters->initialize('admin/foo/bar'); $filters->enableFilters(['role'], 'before'); $filters->enableFilters(['role'], 'after'); $found = $filters->getFilters(); @@ -1005,8 +997,8 @@ public function testEnableNonFilter(): void $filtersConfig = $this->createConfigFromArray(FiltersConfig::class, $config); $filters = $this->createFilters($filtersConfig); - $filters = $filters->initialize('admin/foo/bar'); $filters->enableFilters(['goggle'], 'before'); + $filters->initialize('admin/foo/bar'); } /** @@ -1323,8 +1315,8 @@ public function testFilterClass(): void $expected = [ 'before' => [], 'after' => [ - Multiple1::class, - Multiple2::class, + [Multiple1::class, []], + [Multiple2::class, []], ], ]; $this->assertSame($expected, $filters->getFiltersClass()); @@ -1352,4 +1344,42 @@ public function testReset(): void $this->assertSame(['foo'], $filters->initialize($uri)->getFilters()['before']); $this->assertSame([], $filters->reset()->getFilters()['before']); } + + public function testRunRequiredDoesBefore(): void + { + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $config = [ + 'aliases' => ['google' => GoogleMe::class], + 'required' => [ + 'before' => ['google'], + 'after' => [], + ], + ]; + $filtersConfig = $this->createConfigFromArray(FiltersConfig::class, $config); + $filters = $this->createFilters($filtersConfig); + + $request = $filters->runRequired('before'); + + $this->assertSame('http://google.com', $request->getBody()); + } + + public function testRunRequiredDoesAfter(): void + { + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $config = [ + 'aliases' => ['google' => GoogleMe::class], + 'required' => [ + 'before' => [], + 'after' => ['google'], + ], + ]; + $filtersConfig = $this->createConfigFromArray(FiltersConfig::class, $config); + $filters = $this->createFilters($filtersConfig); + + $response = $filters->runRequired('after'); + + $this->assertSame('http://google.com', $response->getBody()); + } } diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index 06e0f32a10e3..b6b82a947b43 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -43,6 +43,13 @@ The following breaking changes have been made accordingly: - ``TestException`` now extends ``CodeIgniter\Exceptions\LogicException`` instead of ``CodeIgniter\Exceptions\CriticalError``. +Filters Changes +--------------- + +The ``Filters`` class has been changed to allow multiple runs of the same filter +with different arguments in before or after. See +:ref:`Upgrading Guide ` for details. + .. _v460-interface-changes: Interface Changes @@ -106,6 +113,9 @@ The following new Exception interfaces have been added: Commands ======== +- The ``spark routes`` and ``spark filter:check`` commands now display filter + arguments. + Testing ======= @@ -138,6 +148,9 @@ Helpers and Functions Others ====== +- **Filters:** Now you can execute a filter more than once with the different + arguments in before or after. + *************** Message Changes *************** @@ -176,6 +189,11 @@ The following changes have been made accordingly: Deprecations ************ +- **Filters:** + - The properties ``$arguments`` and ``$argumentsClass`` of ``Filters`` have + been deprecated. No longer used. + - The ``Filters::getArguments()`` method has been deprecated. No longer used. + ********** Bugs Fixed ********** diff --git a/user_guide_src/source/incoming/filters.rst b/user_guide_src/source/incoming/filters.rst index 4425ce03d3bf..0697e706e09d 100644 --- a/user_guide_src/source/incoming/filters.rst +++ b/user_guide_src/source/incoming/filters.rst @@ -226,6 +226,9 @@ will be passed in ``$arguments`` to the ``group`` filter's ``before()`` methods. When the URI matches ``admin/users/*'``, the array ``['users.manage']`` will be passed in ``$arguments`` to the ``permission`` filter's ``before()`` methods. +.. note:: Prior to v4.6.0, the same filter cannot be run multiple times with + different arguments. + .. _filter-execution-order: Filter Execution Order diff --git a/user_guide_src/source/incoming/routing.rst b/user_guide_src/source/incoming/routing.rst index 867983437acd..3ab7a5848bc9 100644 --- a/user_guide_src/source/incoming/routing.rst +++ b/user_guide_src/source/incoming/routing.rst @@ -589,7 +589,8 @@ The ``filter`` option passed to the outer ``group()`` are merged with the inner The above code runs ``myfilter1:config`` for the route ``admin``, and ``myfilter1:config`` and ``myfilter2:region`` for the route ``admin/users/list``. -.. note:: The same filter cannot be run multiple times with different arguments. +.. note:: Prior to v4.6.0, the same filter cannot be run multiple times with + different arguments. Any other overlapping options passed to the inner ``group()`` will overwrite their values. diff --git a/user_guide_src/source/installation/upgrade_460.rst b/user_guide_src/source/installation/upgrade_460.rst index 0aa5882e889d..04388acf7d08 100644 --- a/user_guide_src/source/installation/upgrade_460.rst +++ b/user_guide_src/source/installation/upgrade_460.rst @@ -54,6 +54,24 @@ See :ref:`ChangeLog ` for details. Breaking Enhancements ********************* +.. _upgrade-460-filters-changes: + +Filters Changes +=============== + +The ``Filters`` class has been changed to allow multiple runs of the same filter +with different arguments in before or after. + +If you are extending ``Filters``, you will need to modify it to conform to the +following changes: + +- The structure of the array properties ``$filters`` and ``$filtersClasses`` have + been changed. +- The properties ``$arguments`` and ``$argumentsClass`` are no longer used. +- ``Filters`` has been changed so that the same filter class is not instantiated + multiple times. If a filter class is used both before and after, the same instance + is used. + ************* Project Files *************