Basic Usage
The api-tools-content-validation module utilizes Laminas' InputFilter component. API Tools takes information from the UI and writes it to the target API's module configuration file. For more information on the theory of input filters, read the Content Validation introduction.
To configure an input filter in the API Tools UI, browse to the API, then the service. From there, you can see if an existing input filter exist in the "Fields" tab of the service's content. You can add a new field using the "New field" button.
For each field, the same information that is utilized to build an input filter from a factory in ZF2 is the same information that this UI screen will collect to create a service input filter. Each field will accept configuration for the field:
- Is the field required?
- Does the field allow empty content?
- Should the input filter continue processing (i.e., run validators) even if a field is not present or empty?
- A description to identify the purpose of the field.
- A variety of optional filters.
- A variety of optional validators.
When the save button is clicked, this information is sent back to the API Tools API and the
information is then stored in the API's module configuration file, under two separate keys: the
api-tools-content-validation
key and the input_filter_specs
key. Here is a sample:
return [
'api-tools-content-validation' => [
'AddressBook\\V1\\Rest\\Contact\\Controller' => [
'input_filter' => 'AddressBook\\V1\\Rest\\Contact\\Validator',
],
],
'input_filter_specs' => [
'AddressBook\\V1\\Rest\\Contact\\Validator' => [
0 => [
'name' => 'name',
'required' => true,
'filters' => array(),
'validators' => array(),
'allow_empty' => false,
'continue_if_empty' => false,
],
1 => [
'name' => 'email',
'required' => true,
'filters' => [],
'validators' => [
0 => [
'name' => 'Laminas\\Validator\\EmailAddress',
'options' => array(),
],
],
'allow_empty' => false,
'continue_if_empty' => false,
],
2 => [
'name' => 'age',
'required' => true,
'filters' => [],
'validators' => [
0 => [
'name' => 'Laminas\\Validator\\Digits',
'options' => [],
],
],
'allow_empty' => false,
'continue_if_empty' => false,
],
3 => [
'name' => 'notes',
'required' => false,
'filters' => [],
'validators' => [],
'allow_empty' => false,
'continue_if_empty' => false,
],
],
],
];
The above configuration describes the linking of a particular input filter specification with a particular controller service name. Any time a route matches that will eventually attempt to execute a given controller service, if there is an input filter specification for that controller service, this input filter will attempt to filter and validate any deserialized request content body parameters that are present in the request. If it validates, then the MVC lifecycle will continue; if not, then the MVC dispatch process will not execute, and an API Problem response will be returned immediately.
Note: Controller Service Name
The controller service name is the internal name for the service within API Tools, and is representative of the code that the Laminas MVC layer will execute when routing matches the given service.
Accessing Filtered Data
api-tools-content-validation
leaves the request intact once validation is complete. This means that if
you access the request data directly, or, in the case of REST resources, receive request data, you
will have the original, unfiltered data.
If you have performed data normalization as part of your field definition by defining filters, you will likely want the normalized data!
API Tools provides several ways to do this.
Accessing the input filter via RPC controllers
api-tools-content-validation
injects the application's MvcEvent
with the selected input filter once
validation is complete. You can access it via the event parameter
Laminas\ApiTools\ContentValidation\InputFilter
:
$inputFilter = $event->getParam('Laminas\ApiTools\ContentValidation\InputFilter');
RPC controllers compose the MvcEvent
, and you can access it via the getEvent()
method of your
controller; thus, to access the input filter, execute the following:
$event = $this->getEvent();
$inputFilter = $event->getParam('Laminas\ApiTools\ContentValidation\InputFilter');
Be aware that the input filter may not be defined! Test it before performing operations on it:
if ($inputFilter) {
// do something with the input filter
}
Accessing the input filter from REST resources
API Tools injects the ResourceEvent
for REST resources with any input filter discovered in the
MvcEvent
. Further, the base AbstractResourceListener
provides a getInputFilter()
method that
proxies to the ResourceEvent
to give you access to the input filter:
$inputFilter = $this->getInputFilter();
Be aware that the input filter may not be defined! Test it before performing operations on it:
if ($inputFilter) {
// do something with the input filter
}
Via dependency injection
Since input filters are named services, you can also pull them from the service manager within factories in order to inject your object.
For an example, the above examples define an input filter by the name
AddressBook\V1\Rest\Contact\Validator
. Let's define our ContactResource
to receive the input
filter via constructor injection (along with a mapper object we've defined):
namespace AddressBook\V1\Rest\Contact;
use Laminas\InputFilter\InputFilterInterface;
use Laminas\ApiTools\ApiProblem\ApiProblem;
use Laminas\ApiTools\Rest\AbstractResourceListener;
class ContactResource extends AbstractResourceListener
{
protected $inputFilter;
protected $mapper;
public function __construct(Mapper $mapper, InputFilterInterface $inputFilter)
{
$this->mapper = $mapper;
$this->inputFilter = $inputFilter;
}
}
Now, let's write a service factory that injects these into our ContactResource
on instantiation:
namespace AddressBook\V1\Rest\Contact;
class ContactResourceFactory
{
public function __invoke($services)
{
// We'll assume that the mapper has been added to the service manager
$mapper = $services->get('AddressBook\V1\Rest\Contact\Mapper');
// Grab the input filter:
$inputFilter = $services->get('AddressBook\V1\Rest\Contact\Validator');
return new ContactResource($mapper, $inputFilter);
}
}
Retrieving normalized fields
Once you have the input filter, you can retrieve the normalized fields. Typically, you will retrieve all fields at once:
$fields = $inputFilter->getValues();
The above returns an associative array (potentially nested) of normalized values.
You can also retrieve the original, unfiltered data:
$unfiltered = $inputFilter->getRawValues();
Or individual values by name:
$value = $inputFilter->getValue('fieldName');
$unfilteredValue = $inputFilter->getRawValue('fieldName');
The input filter ignores values passed to it that are not part of its definition.