-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #208 from gsteel/v3/immutable-filter-chain
Refactor `FilterChain`
- Loading branch information
Showing
13 changed files
with
681 additions
and
336 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,93 +1,155 @@ | ||
# Filter Chains | ||
|
||
> MISSING: **Installation Requirements** | ||
> The following examples use the [`Alpha` filter from the `laminas/laminas-i18n`](https://docs.laminas.dev/laminas-i18n/filters/alpha/). | ||
> Make sure to install the required package before running the examples. | ||
> | ||
> ```bash | ||
> $ composer require laminas/laminas-i18n | ||
> ``` | ||
Often, multiple filters should be applied to some value in a particular order. | ||
For example, a login form accepts a username that should be lowercase and | ||
contain only alphabetic characters. | ||
`Laminas\Filter\FilterChain` provides a simple method by which filters may be | ||
chained together. The following code illustrates how to chain together two | ||
filters for the submitted username and fulfill the above requirements: | ||
Filter chains provide a way of grouping multiple filters together. | ||
They operate on the input by fulfilling the `FilterInterface` contract in the same way as all the other filters. | ||
|
||
You can enqueue as many filters as you like via a straight-forward api, attaching either ready-to-use filter instances, arbitrary callables or by using a declarative array format. | ||
|
||
## Getting a Filter Chain Instance | ||
|
||
The filter chain has a constructor dependency on the `FilterPluginManager`, so whilst you can manually create a filter chain from scratch, it is normally easier to retrieve one from the plugin manager | ||
|
||
```php | ||
// Create a filter chain and add filters to the chain | ||
$filterChain = new Laminas\Filter\FilterChain(); | ||
$filterChain | ||
->attach(new Laminas\I18n\Filter\Alpha()) | ||
->attach(new Laminas\Filter\StringToLower()); | ||
assert($container instanceof Psr\Container\ContainerInterface); | ||
$pluginManager = $container->get(Laminas\Filter\FilterPluginManager::class); | ||
|
||
// Filter the username | ||
$username = $filterChain->filter($_POST['username']); | ||
``` | ||
// Create a Filter Chain instance manually: | ||
$chain = new Laminas\Filter\FilterChain($pluginManager); | ||
|
||
Filters are run in the order they are added to the filter chain. In the above | ||
example, the username is first removed of any non-alphabetic characters, and | ||
then any uppercase characters are converted to lowercase. | ||
// Fetch an empty filter chain from the plugin manager | ||
$chain = $pluginManager->get(Laminas\Filter\FilterChain::class); | ||
|
||
Any object that implements `Laminas\Filter\FilterInterface` may be used in a | ||
filter chain. | ||
// Build a ready-to-use filter chain via the plugin manager | ||
$chain = $pluginManager->build(Laminas\Filter\FilterChain::class, [ | ||
// ...(Filter Chain Configuration)) | ||
]); | ||
``` | ||
|
||
## Setting Filter Chain Order | ||
## Adding Filters to the Chain | ||
|
||
For each filter added to the `FilterChain`, you can set a priority to define | ||
the chain order. Higher values indicate higher priority (execute first), while | ||
lower and/or negative values indicate lower priority (execute last). The default value is `1000`. | ||
The following examples provide 3 ways of building a chain that performs the following: | ||
|
||
In the following example, any uppercase characters are converted to lowercase | ||
before any non-alphabetic characters are removed. | ||
- Trim the input string using `Laminas\Filter\StringTrim` | ||
- Make the string lowercase using `Laminas\Filter\StringToLower` | ||
- Reverse the string using a closure | ||
|
||
### 1. Attaching Filter Instances to the Chain | ||
|
||
```php | ||
// Create a filter chain and add filters to the chain | ||
$filterChain = new Laminas\Filter\FilterChain(); | ||
$filterChain | ||
->attach(new Laminas\I18n\Filter\Alpha()) | ||
->attach(new Laminas\Filter\StringToLower(), 500); | ||
use Laminas\Filter\FilterChain; | ||
use Laminas\Filter\StringToLower; | ||
use Laminas\Filter\StringTrim; | ||
|
||
$pluginManager = $container->get(Laminas\Filter\FilterPluginManager::class); | ||
|
||
$chain = $pluginManager->get(FilterChain::class); | ||
|
||
$chain->attach(new StringTrim()); // directly instantiate the filter | ||
$chain->attach($pluginManager->get(StringToLower::class)); | ||
$chain->attach(static fn (string $value): string => strrev($value)); | ||
|
||
print $chain->filter(' OOF '); // 'foo' | ||
``` | ||
|
||
## Using the Plugin Manager | ||
### 2. Building the Chain with Array Configuration | ||
|
||
A `FilterPluginManager` is attached to every `FilterChain` instance. Every filter | ||
that is used in a `FilterChain` must be known to the `FilterPluginManager`. | ||
```php | ||
use Laminas\Filter\FilterChain; | ||
use Laminas\Filter\StringToLower; | ||
use Laminas\Filter\StringTrim; | ||
|
||
$pluginManager = $container->get(Laminas\Filter\FilterPluginManager::class); | ||
$chain = $pluginManager->build(FilterChain::class, [ | ||
'filters' => [ | ||
['name' => StringTrim::class], | ||
['name' => StringToLower::class], | ||
], | ||
'callbacks' => [ | ||
['callback' => static fn (string $value): string => strrev($value)], | ||
], | ||
]); | ||
|
||
print $chain->filter(' OOF '); // 'foo' | ||
``` | ||
|
||
To add a filter to the `FilterChain`, use the `attachByName()` method. The | ||
first parameter is the name of the filter within the `FilterPluginManager`. The | ||
second parameter takes any options for creating the filter instance. The third | ||
parameter is the priority. | ||
### 3. Adding Filters to the Chain by Name | ||
|
||
```php | ||
// Create a filter chain and add filters to the chain | ||
$filterChain = new Laminas\Filter\FilterChain(); | ||
$filterChain | ||
->attachByName('alpha') | ||
->attachByName('stringtolower', ['encoding' => 'utf-8'], 500); | ||
use Laminas\Filter\FilterChain; | ||
use Laminas\Filter\StringToLower; | ||
use Laminas\Filter\StringTrim; | ||
|
||
$pluginManager = $container->get(Laminas\Filter\FilterPluginManager::class); | ||
|
||
$chain = $pluginManager->get(FilterChain::class); | ||
|
||
// `attachByName` retrieves the filter from the composed plugin manager: | ||
$chain->attachByName(StringTrim::class); | ||
$chain->attachByName(StringToLower::class); | ||
// We must still use `attach` to add the closure: | ||
$chain->attach(static fn (string $value): string => strrev($value)); | ||
|
||
print $chain->filter(' OOF '); // 'foo' | ||
``` | ||
|
||
The following example shows how to add a custom filter to the `FilterPluginManager` and the | ||
`FilterChain`: | ||
By default, filters execute in the order they are added to the filter chain. | ||
In the above examples, the input is trimmed first, then converted to lowercase and finally reversed. | ||
|
||
## Types of Attachable Filters | ||
|
||
Any object that implements `Laminas\Filter\FilterInterface` may be used in a filter chain. | ||
Additionally, you can provide any type of `callable`, however it should match the signature `fn (mixed $value): mixed`. You are free to narrow the types from mixed, but given the simple contract of `FilterInterface` it is often better practice to [write a custom filter](writing-filters.md) and register it with the plugin manager. | ||
|
||
## Setting Filter Chain Order | ||
|
||
For each filter added to the `FilterChain`, you can set a priority to define the chain order. | ||
Higher values indicate higher priority (execute first), while lower and/or negative values indicate lower priority (execute last). | ||
The default priority is `1000`. | ||
|
||
In the following example, an uppercase prefix is applied after the input has been converted to lower case, even though the prefix filter is added to the chain first: | ||
|
||
```php | ||
$filterChain = new Laminas\Filter\FilterChain(); | ||
$filterChain | ||
->getPluginManager() | ||
->setInvokableClass('myNewFilter', 'MyCustom\Filter\MyNewFilter'); | ||
$filterChain | ||
->attachByName('alpha') | ||
->attachByName('myNewFilter'); | ||
// Create a filter chain and add filters to the chain | ||
$filterChain = $pluginManager->get(Laminas\Filter\FilterChain::class); | ||
$filterChain->attach(new Laminas\Filter\StringPrefix(['prefix' => 'FOO: '])); | ||
$filterChain->attach(new Laminas\Filter\StringToLower(), 500); | ||
|
||
print $filterChain->filter('BAR'); // 'FOO: bar' | ||
``` | ||
|
||
You can also add your own `FilterPluginManager` implementation: | ||
## Array Configuration | ||
|
||
As previously noted, you can define filter chains using a configuration array. | ||
The exact specification of this array is as follows: | ||
|
||
```php | ||
$filterChain = new Laminas\Filter\FilterChain(); | ||
$filterChain->setPluginManager(new MyFilterPluginManager()); | ||
$filterChain | ||
->attach(new Laminas\I18n\Filter\Alpha()) | ||
->attach(new MyCustom\Filter\MyNewFilter()); | ||
$filterChainConfig = [ | ||
'filters' => [ | ||
[ | ||
'name' => SomeFilter::class, // Required. Must be an alias or a FQCN registered in the plugin manager | ||
'options' => [ /* ... */ ], // Optional. Provide options specific to the required filter | ||
'priority' => 500, // Optional. Set the execution priority of the filter (Default 1000) | ||
], | ||
], | ||
'callbacks' => [ | ||
[ | ||
'callback' => static fn (string $in): string => strrev($in), // Required. Any type of PHP callable | ||
'priority' => 500, // Optional priority, default 1000 | ||
], | ||
[ | ||
'callback' => new Laminas\Filter\StringToLower(), // Any object implementing FilterInterface | ||
], | ||
], | ||
]; | ||
``` | ||
|
||
NOTE: **Callbacks are Registered First** | ||
It's important to note that internally, `callbacks` are registered _first_. | ||
This means that if you do not specify priorities when using the array configuration format, the filter execution order may not be what you want. | ||
|
||
## Using the Plugin Manager | ||
|
||
As with other plugin managers in the laminas ecosystem, you can retrieve filters either by fully qualified class name, or by any configured alias of that class. | ||
For example, the `Laminas\Filter\StringToLower` filter is aliased to `stringToLower`, therefore, calling `$pluginManager->get('stringToLower')` will yield an instance of this filter. | ||
|
||
When using filter chain, you must remember to [register custom filters](writing-filters.md#registering-custom-filters-with-the-plugin-manager) with the plugin manager correctly if you wish to reference your filters by FQCN or alias. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.