The spiral/filters
is a powerful component for filtering and optional validating input data. It allows you to define a
set of rules for each input field, and then use those rules to ensure that the input data is in the correct format and
meets any other requirements you have set. You can use filters to validate data from various sources like HTTP requests,
gRPC requests, console commands, and others.
Here is a simple example of a filter:
namespace App\Endpoint\Web\Filter;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
final class UserFilter extends Filter
{
#[Query(key: 'username')]
public string $username;
}
This example shows a simple filter that can be requested in a controller action and will automatically map data from the HTTP request to the filter properties. Optionally, you can define a set of validation rules for each property to ensure that the input data is in the correct format.
One of the benefits of using filters is that at it helps to centralize your input validation logic in a single place. This can make it easier to maintain your code, as you don't need to duplicate validation logic in multiple places throughout your application.
Additionally, filters can be reused across different parts of your application, which can help to reduce code duplication and make it easier to manage your validation logic.
Illustration of the process of filtering and validating input data in an HTTP layer
See more
Read more about how to use filters for console commands in the Cookbook — Console command input validation section.
Note
The component relies on Validation component, make sure to read it first if you want to use validation features.
The component does not require any configuration and can be activated using the
bootloader Spiral\Bootloader\Security\FiltersBootloader
:
public function defineBootloaders(): array
{
return [
// ...
\Spiral\Bootloader\Security\FiltersBootloader::class,
// ...
];
}
Read more about bootloaders in the Framework — Bootloaders section.
The filter components operate using the Spiral\Filter\InputInterface
as a primary data source:
interface InputInterface
{
public function withPrefix(string $prefix, bool $add = true): InputInterface;
public function getValue(string $source, string $name = null);
}
By default, this interface is bound to InputManager and allows to access any request's attribute using a source and origin pair with dot-notation support.
For example:
namespace App\Endpoint\Web;
use Spiral\Filters\InputInterface;
class HomeController
{
public function index(InputInterface $input): void
{
dump($input->getValue('query', 'abc')); // ?abc=1
// dot notation
dump($input->getValue('query', 'a.b.c')); // ?a[b][c]=2
// same as above
dump($input->withPrefix('a')->getValue('query', 'b.c')); // ?a[b][c]=2
}
}
Input binding is the primary way of delivering data from request into the filter object.
The implementation of the filter object might vary from package to package. The default implementation is provided via
the abstract class Spiral\Filters\Model\Filter
.
To create a custom filter to validate a simple query value with key username
, use the scaffolding command:
php app.php create:filter UserFilter -p username:query
Note
Read more about scaffolding in the Basics — Scaffolding section.
After executing this command, the following output will confirm the successful creation:
Declaration of 'UserFilter' has been successfully written into 'app/src/Endpoint/Web/Filter/UserFilter.php'.
namespace App\Endpoint\Web\Filter;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
final class UserFilter extends Filter
{
#[Query(key: 'username')]
public string $username;
}
Warning
Be careful when using typed properties in your filter. Filter does not perform any type casting and will throw an exception if the input data does not match the property type.
You can request the Filter as a method injection (it will be automatically bound to the current HTTP request input):
namespace App\Endpoint\Web;
class UserController
{
public function show(Filter\UserFilter $filter): void
{
dump($filter->username);
}
}
For more advanced scenarios, where data needs to be transformed into custom types, filter input casters come into play.
Before diving into the application of casters, it's essential to comprehend
the Spiral\Filters\Model\Mapper\CasterInterface
.
use Spiral\Filters\Model\FilterInterface;
interface CasterInterface
{
public function supports(\ReflectionNamedType $type): bool;
public function setValue(FilterInterface $filter, \ReflectionProperty $property, mixed $value): void;
}
This interface has two pivotal methods:
$type
and lets you decide whether this value is castable with this caster or not.The component provides a set of casters out of the box:
Transforms strings into Ramsey\Uuid\Uuid
objects.
namespace App\Endpoint\Web\Filter;
use Ramsey\Uuid\UuidInterface;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
final class UserFilter extends Filter
{
#[Query]
public UuidInterface $uuid;
}
Enables casting of strings into enums, promoting type safety.
namespace App\Endpoint\Web\Filter;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
final class UserFilter extends Filter
{
#[Query]
public RoleEnum $role;
}
Here is an example of a simple caster:
Consider a scenario where you're handling a UUID string and intend to convert this into a UUID object.
use Spiral\Filters\Model\FilterInterface;
use Spiral\Filters\Model\Mapper\CasterInterface;
use Ramsey\Uuid\UuidInterface;
use Ramsey\Uuid\Uuid;
final class UuidCaster implements CasterInterface
{
public function supports(\ReflectionNamedType $type): bool
{
return $type->getName() === UuidInterface::class;
}
public function setValue(FilterInterface $filter, \ReflectionProperty $property, mixed $value): void
{
$property->setValue($filter, Uuid::fromString($value));
}
}
Note
Uuid caster is already provided by the component.
To make your custom caster operational, you need to register it within the application's bootloader.
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Filters\Model\Mapper\CasterRegistryInterface;
class AppBootloader extends Bootloader
{
public function boot(CasterRegistryInterface $casterRegistry)
{
$casterRegistry->register(new UuidCaster());
}
}
After registering the caster, whenever you define filters with properties that match the caster's supported types, Spiral will automatically employ the registered caster to transform the data.
When dealing with request data filters, it's crucial to ensure that these parameters match the expected data types. For
instance, a parameter expected as a string
should not be processed if it comes in a different format, like an array
or an integer
. The Spiral Framework offers an effective solution to handle such type mismatches gracefully.
You can use the Spiral\Filters\Attribute\CastingErrorMessage
attribute for any property that requires type validation:
namespace App\Endpoint\Web\Filter;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Attribute\CastingErrorMessage;
final class UserFilter extends Filter
{
#[Query(key: 'username')]
#[CastingErrorMessage('Invalid type')]
public string $username;
}
In this scenario, the username
is expected to be a string. However, there might be instances where the input data is
of the wrong type, such as an array
or an integer
. In such cases, the filter will catch the exception and return the
validation error message.
By default, filters do not perform validation. However, if you want to validate a filter, you can implement the
HasFilterDefinition
interface and define a set of validation rules for the filter properties using
the FilterDefinition
class with Spiral\Filters\Model\ShouldBeValidated
interface implementation:
namespace App\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\ShouldBeValidated;
final class MyFilterDefinition implements FilterDefinitionInterface, ShouldBeValidated
{
public function __construct(
private readonly array $validationRules = [],
private readonly array $mappingSchema = []
) {
}
public function validationRules(): array
{
return $this->validationRules;
}
public function mappingSchema(): array
{
return $this->mappingSchema;
}
}
Here is an example of registering a filter definition and binding with a validator that will be used to validate filters
with the MyFilterDefinition
definition:
namespace App\Application\Bootloader;
use App\Validation;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Validation\Bootloader\ValidationBootloader;
use Spiral\Validation\ValidationInterface;
use Spiral\Validation\ValidationProvider;
final class ValidatorBootloader extends Bootloader
{
public function boot(ValidationProvider $provider): void
{
$provider->register(
\App\Filter\MyFilterDefinition::class,
static fn(Validation $validation): ValidationInterface => new MyValidation()
);
}
}
Note
Red more about Validation component here .
And now you can use the filter definition in your filter:
namespace App\Endpoint\Web\Filter;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
use App\Filter\MyFilterDefinition;
final class UserFilter extends Filter implements HasFilterDefinition
{
#[Query]
public string $username;
public function filterDefinition(): FilterDefinitionInterface
{
return new MyFilterDefinition([
'username' => ['string', 'required']
]);
}
}
Note
Try URL with?username=john
. TheUserFilter
will automatically pre-validate your request before delivering it to the controller.