Validation
FormDataProcessor processes the data that comes with a form: it casts, sanitizes, transforms and validates the value of each field, and gives back the processed data and the errors. The rules are the ones of derafu/data-processor. Nobody writes them for the common cases: they are derived from the schema of the form, and the rules section of the definition adds the rest.
$result = $processor->process($form, $_POST);
if ($result->hasErrors()) {
$errors = $result->getErrors(); // ['field' => ['message']]
$form = $result->getForm(); // The form with its errors, to render it again.
} else {
$data = $result->getProcessedData();
}
Where the rules come from
For each field the processor joins three sources, in this order:
- The property of the schema: its
type,format,pattern, limits,enum, etc. (see below). - The control of the UI schema: a control of type
fileorimagevalidates a file or an image, and a control of typeeditorremoves the tags of the HTML. - The
rulessection of the definition: explicit rules by field.
'rules' => [
'name' => [
'transform' => ['uppercase'],
'validate' => ['min_length:3'],
],
],
The rules of a field are written in four categories, and are applied in this order: cast, transform, sanitize, validate. The explicit ones are joined to the derived ones like this:
cast: the explicit one replaces the derived one. When the type of the cast changes, the derived sanitizers (for exampletrim, that is for strings) are dropped, and the explicit ones are kept.sanitize,transformandvalidate: the explicit ones are added after the derived ones.required: the fields that are required (see Required fields) have it as the first rule ofvalidate, once.
A field whose rule of the UI schema is not active (it is hidden, for example) is not validated and is not in the processed data, even if it came with the data.
Objects
The properties of an object of the schema are fields of the form with the name of their path, address.city, and the data has them inside the object, as PHP reads a form (address[city] in the name of the control):
'schema' => [
'type' => 'object',
'required' => ['contact'],
'properties' => [
'contact' => [
'type' => 'object',
'required' => ['address', 'zone'],
'properties' => [
'address' => ['type' => 'string'],
'zone' => ['type' => 'string'],
'city' => ['type' => 'string'],
],
],
],
],
'uischema' => [
'type' => 'VerticalLayout',
'elements' => [
['type' => 'Control', 'scope' => '#/properties/contact/properties/address'],
['type' => 'Control', 'scope' => '#/properties/contact/properties/zone'],
['type' => 'Control', 'scope' => '#/properties/contact/properties/city'],
],
],
$result = $processor->process($form, ['contact' => ['address' => 'Street 1', 'zone' => 'North', 'city' => '']]);
$result->getProcessedData(); // ['contact' => ['address' => 'Street 1', 'zone' => 'North', 'city' => '']]
$result->getErrors(); // ['contact.zone' => [...]] when the zone is missing.
- The processed data and the data that comes back to the form have the same shape as the data that came.
- The errors have the name of the path of the field (
contact.zone). - A field is required by the
requiredof its own object:addressandzoneare,cityis not. Therequiredof the root only asks for the keycontact: it does not make any of its fields required. - A key with the whole name (
'contact.address') is the value of that field too. - The conditions of the rules of the UI schema can be on a field of an object (
#/properties/contact/properties/city).
What the schema validates
| In the schema | Rule |
|---|---|
type: string |
cast string; sanitize trim |
type: integer |
cast integer; validate int |
type: number |
cast float; validate numeric |
type: boolean |
cast boolean |
minLength, maxLength |
min_length, max_length |
minimum, maximum (integer and number) |
gte, lte |
minItems, maxItems, uniqueItems |
min_items, max_items, unique |
pattern |
regex (see below) |
enum, or oneOf with const |
in with the values |
contentMediaType: application/json |
json |
format |
see the table below |
A string with format: email is also transformed to lowercase.
Formats
format |
Rule |
|---|---|
email |
email |
uri, url |
url |
date |
date_format:Y-m-d |
date-time |
date_format:Y-m-d H:i:s |
time |
date_format:H:i:s |
tel |
regex of telephone numbers |
hostname |
hostname |
ipv4, ipv6 |
ip |
uuid |
uuid |
base64 |
base64 |
json |
json |
regex |
regex_pattern: the value must be a regular expression |
| another | string |
Patterns
The pattern of JSON Schema is written without delimiters, like the browsers read the pattern attribute of HTML. PHP needs them, so the form gives the pattern the slash as its delimiter and escapes the slashes that it has, once: ^a/b$ and ^a\/b$ are the same pattern. The same conversion is used by the patterns of the types and by the conditions of the rules of the UI schema.
The regular expression is a PCRE one, the dialect of PHP. A pattern that can not be compiled is an error of the definition, not of the value: it is reported when the field is processed. A field with format: regex is the opposite: the value is the pattern, and it is invalid if it can not be compiled.
Booleans
For JSON Schema, required says that the key must be in the data, not that it has a given value. A boolean that is only required is valid when it is checked and when it is not. To ask for a value, fix it in the schema:
'terms' => [
'type' => 'boolean',
'title' => 'I accept the terms',
'const' => true, // or 'enum' => [true]
],
const: trueorenum: [true]validate withaccepted: the value must betrue.const: falseorenum: [false]validate withdeclined: the value must befalse.
A browser does not send a checkbox that is not checked, so a missing key is read as false, and the form that asks for true fails. The control has the native required too, see Required fields.
Translated errors
The messages of the errors are translated when the processor has a translator, see Translations.