The filter object used to perform complex data validation and filtration using PSR-7 or any other input. You
can create Filter manually or using scaffolder php app.php create:filter my
:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
];
protected const VALIDATES = [
];
protected const SETTERS = [
];
}
Use Spiral\Filter\FilterProviderInterface
->createFilter
or simply request filter dependency to create an instance:
use App\Filter\MyFilter;
class HomeController
{
public function index(MyFilter $filter)
{
dump($filter);
}
}
The core of any filter object is SCHEMA
; this constant defines mapping between fields and values provided by input.
Every key pair is defined as field
=> source:origin
or field
=> source
. The source is the subset of data
from user input. In the HTTP scope, the sources can be cookie
, data
, query
, input
(data
+query
), header
,
file
, server
. The origin is the name of the external field (dot notation is supported).
Note
You can use any input bag from InputManager as source.
For example, we can tell our Filter to point field login
to the QUERY param username
:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'login' => 'query:username'
];
}
You can combine multiple sources inside the Filter:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'redirectTo' => 'query:redirectURL',
'memberCookie' => 'cookie:memberCookie',
'username' => 'data:username',
'password' => 'data:password',
'rememberMe' => 'data:rememberMe'
];
}
Note
The most common source isdata
(points to PSR-7 - parsed body), you can use this data to fetch values from incoming JSON payloads.
The data origin can be specified using dot notation pointing to some nested structure. For example:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'firstName' => 'data:name.first'
];
protected const VALIDATES = [
'firstName' => [
['notEmpty']
]
];
protected const SETTERS = [
'firstName' => 'strval'
];
}
We can accept and validate the following data structure:
{
"names": {
"first": "Antony"
}
}
Note
The error messages will be correctly mounted into the original location. You can also use composite filters for more complex use-cases.
By design, you can use any method of [InputManager](/docs/http-request-response/2.10/en)
as source where origin is passed
parameter. Following sources are available:
Source | Description |
---|---|
uri | Current page Uri in a form of Psr\Http\Message\UriInterface |
path | Current page path |
method | Http method (GET, POST, ...) |
isSecure | If https used. |
isAjax | If X-Requested-With set as xmlhttprequest |
isJsonExpected | When client expects application/json |
remoteAddress | User ip address |
Read more about InputManager here.
For example to check if a user request made over https:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'httpsRequest' => 'isSecure'
];
protected const VALIDATES = [
'httpsRequest' => [
['notEmpty', 'error' => 'Connection is not secure.']
]
];
}
Note
Read more about the validation below.
Every route writes matched parameters into ServerRequestInterface attribute matches
, is it possible to access route
values inside your filter using attribute:matches.{name}
notation:
$router->setRoute(
'sample',
new Route('/action/<id>.html', new Controller(HomeController::class))
);
Filter definition:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'routeID' => 'attribute:matches.id'
];
}
Use setters to typecast the incoming value before passing it to the validator. The Filter will assign null to the value in case of typecast error:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'number' => 'query:number'
];
protected const VALIDATES = [
'number' => [
['notEmpty'],
['number::higher', 5]
]
];
protected const SETTERS = [
'number' => 'intval'
];
}
Note
You can use any of the default PHP functions likeintval
,strval
etc.
namespace App\Controller;
use App\Filter\MyFilter;
class HomeController
{
public function index(MyFilter $filter)
{
dump($filter->number); // always int
}
}
The validation rules can be defined using same approach as in validation component.
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'name' => 'data:name'
];
protected const VALIDATES = [
'name' => [
['notEmpty']
]
];
}
You can use all the checkers, conditions, and rules.
You can specify the custom error message to any of the rules similar way as in the validation component.
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'name' => 'data:name'
];
protected const VALIDATES = [
'name' => [
['notEmpty', 'error' => 'Name must not be empty']
]
];
}
If you plan to localize error message later, wrap the text in [[]]
to automatically index and replace the translation:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'name' => 'data:name'
];
protected const VALIDATES = [
'name' => [
['notEmpty', 'error' => '[[Name must not be empty]]']
]
];
}
Once the Filter configured you can access its fields (filtered data), check if the data valid and return the set of errors in case of failure.
Note
Use Domain Core Interceptors to validate your filters before they will arrive to the controller.
To get a filtered list of fields, use methods getField
and getFields
. For the Filter like that:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'name' => 'data:name',
'email' => 'data:email'
];
protected const VALIDATES = [
'name' => [
['notEmpty']
]
];
protected const SETTERS = [
];
}
Following fields are available:
public function index(MyFilter $filter)
{
dump($filter->getFields()); // {name: ..., email: ...}
dump($filter->getField('name'));
// same as above
dump($filter->email);
}
To check if filter is valid use isValid
, list of field errors is available via getErrors
:
public function index(MyFilter $filter)
{
if (!$filter->isValid()) {
dump($filter->getErrors());
}
}
The errors automatically mapped to the origin property name, for example:
namespace App\Filter;
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'name' => 'data:names.name.0'
];
protected const VALIDATES = [
'name' => [
['notEmpty']
]
];
protected const SETTERS = [
];
}
Will produce the following error if the field name
is invalid:
{
"status": 400,
"errors": {
"names": {
"name": "This value is required."
}
}
}
The error format is identical to one described in validation.
You can extend one filter from another, the schema, validation, and setters will be inherited:
namespace App\Filter;
class MyFilter extends BaseFilter
{
protected const SCHEMA = [
'name' => 'data:name'
];
protected const VALIDATES = [
'name' => [
['notEmpty']
]
];
}
Where BaseFilter
is:
namespace App\Filter;
use Spiral\Filters\Filter;
class BaseFilter extends Filter
{
protected const SCHEMA = [
'token' => 'data:token'
];
protected const VALIDATES = [
'token' => [
['notEmpty']
]
];
}
Now filter MyFilter
will require token
value as well:
{
"status": 400,
"errors": {
"token": "This value is required.",
"name": "This value is required."
}
}