Data validation
1Introduction
Temma provides a comprehensive data validation system, both for input (data received by the server) and output (data sent to the browser).
Three levels of usage are available:
- PHP attributes (declarative approach): the Check\* attributes allow you to validate data directly on controllers and actions, without writing validation code. This is the simplest and most common approach.
- Request object methods (programmatic approach): for cases where attributes are not sufficient, the validate*() methods of the Request object provide full control from within controller code.
- The DataFilter object (low level): the foundational building block on which the entire system is built. Can be used anywhere in your code to validate any data.
The common thread throughout the entire system is the validation contract: a definition that describes the expected data format.
2Validation contracts
2.1Contract format
A validation contract can be written in two forms:
String format — parameters are separated by semicolons:
// an integer between 0 and 100
'int; min: 0; max: 100'
// a string from 2 to 50 characters
'string; minLen: 2; maxLen: 50'
// an email address matching a pattern
'email; mask: @mydomain\.com$'
Array format — each parameter is an array key:
// an integer between 0 and 100
['type' => 'int', 'min' => 0, 'max' => 100]
// an associative array with typed keys
[
'type' => 'assoc',
'keys' => [
'id' => 'int',
'name' => 'string; minLen: 2',
'email' => 'email',
]
]
Both formats are interchangeable and can be used wherever a contract is expected. For full details, see the DataFilter documentation.
2.2Strict and non-strict mode
By default, validation is non-strict: data is converted when possible (a string "42" is accepted for an int contract), out-of-range values are adjusted (a number that is too large is clamped to the maximum), and strings that are too long are truncated.
In strict mode, no conversion is performed: data must exactly match the expected type, and any limit violation causes an error.
The mode can be controlled in several ways:
- Globally, via the $strict parameter of attributes or validation methods.
- Per contract, by prefixing the type: = to force strict mode (e.g. '=int'), ~ to force non-strict mode (e.g. '~string').
2.3Available types
Temma provides a wide range of validation types:
- Scalar: null, bool, true, false, int, float, string
- Text and identifiers: email, url, slug, uuid, color, phone
- Dates and times: date, time, datetime
- Network: ip, ipv4, ipv6, mac, port
- Codes: isbn, ean, hash (and aliases md5, sha1, sha256, sha512)
- Geography: geo
- Structures: enum, list, assoc
- Binary data: json, binary, base64
Types can be combined with the pipe operator: 'null|int|string', and made nullable with the ? prefix: '?bool'.
For the full details of each type and its parameters, see the DataFilter documentation.
2.4Universal parameters
Several parameters are available for all (or most) types:
- default: fallback value if the data is invalid.
- min / max: minimum and maximum values (numbers, dates, coordinates).
- minLen / maxLen: minimum and maximum length (strings, lists). maxLen supports K, M, G units.
- mask: regular expression for validation (strings, emails, URLs).
- values: accepted values (for enum).
- contract: validation contract for elements of a list or json.
- keys: expected keys in an assoc array.
- mime: allowed MIME types (binary, base64).
- charset: character encoding.
- format / inFormat / outFormat: date/time formats.
2.5Optional keys and wildcard
In assoc-type contracts (and in GET/POST parameters), keys can be marked as optional with the ? suffix:
[
'name' => 'string', // required
'firstname?' => 'string', // optional
'age' => 'int; min: 0', // required
]
The ... wildcard allows accepting additional data not defined in the contract. It can optionally enforce a type:
// accept all additional keys as-is
['name' => 'string', '...']
// accept additional keys, but they must be integers
['name' => 'string', '...' => 'int']
Without the wildcard, in non-strict mode undefined keys are removed; in strict mode, they cause an error.
3Named contracts (configuration)
To avoid duplicating validation contracts, you can define them once in the configuration file etc/temma.php, under the validationTypes key:
<?php
return [
'validationTypes' => [
// simple contract (enumeration)
'sitecolor' => 'enum; values: orange, yellow, lime, green',
// complex contract (associative array)
'user' => [
'type' => 'assoc',
'keys' => [
'id?' => 'int',
'login' => 'string',
'email' => 'email',
],
],
// custom validation object
'category' => '\App\Validations\CategoryValidator',
],
];
These named contracts can then be used anywhere a contract is expected (Check\* attributes, validate*() methods, DataFilter object), simply by passing their name as a string:
// in an attribute
#[TµCheckPost('user')]
// in a validation method
$this->_request->validateInput('user');
// in DataFilter
\Temma\Utils\DataFilter::process($data, 'user');
4Input validation (Check attributes)
4.1The five attributes
The Check\* attributes allow you to validate incoming data declaratively, directly on controllers and actions:
- \Temma\Attributes\Check\Params: URL parameters.
- \Temma\Attributes\Check\Get: GET parameters.
- \Temma\Attributes\Check\Post: POST parameters.
- \Temma\Attributes\Check\Files: uploaded files.
- \Temma\Attributes\Check\Payload: request body (JSON, base64, binary).
Example: validate that a POST form contains an email and a name:
use \Temma\Attributes\Check\Post as TµCheckPost;
class User extends \Temma\Web\Controller {
// on error, redirects to the HTTP referer;
// received POST data is copied to the default
// '__form' flash variable
#[TµCheckPost([
'email' => 'email',
'name' => 'string; minLen: 2',
])]
public function create() {
// data is valid, we can use it
}
}
Example: validate URL parameters of an action:
use \Temma\Attributes\Check\Params as TµCheckParams;
class Article extends \Temma\Web\Controller {
// expects a positive integer and a string (slug)
#[TµCheckParams(['int; min: 1', 'slug'])]
public function show(int $id, string $slug) {
// ...
}
}
Example: validate a JSON payload:
use \Temma\Attributes\Check\Payload as TµCheckPayload;
class Api extends \Temma\Web\Controller {
// expects a JSON stream containing an associative array;
// on error, redirects to '/api/error';
// the '__apiErr' flash variable is set to true
#[TµCheckPayload(
[
'type' => 'json',
'contract' => [
'type' => 'assoc',
'keys' => [
'id' => 'int',
'role' => 'enum; values: user, admin',
],
],
],
redirect: '/api/error',
flashVar: 'apiErr',
)]
public function update() {
// ...
}
}
For full details and more examples, see the Check attributes documentation.
4.2Common parameters
All Check\* attributes (except Output) accept common parameters to control the behavior on validation failure:
- $strict: (bool) enables strict validation mode (false by default).
- $redirect: (string) redirection URL on error.
- $redirectVar: (string) name of the template variable containing the redirection URL.
- $redirectReferer: (bool) true by default. If true and $redirect and $redirectVar are empty, the HTTP REFERER header is used as the redirection URL.
- $flashVar: (string) name of the flash variable that will contain received data on redirection ('form' by default, making data accessible in __form). Set to null to disable.
4.3Redirection priority
When data is invalid, the redirection URL is determined in the following order:
- The $redirect parameter, if defined.
- The content of the template variable specified by $redirectVar, if it exists and is non-empty.
- The HTTP Referer header, if $redirectReferer is true and the header is present.
- The redirect key from the x-security extended configuration in etc/temma.php.
If no URL is found, an HTTP 403 (Forbidden) error is returned.
5Output validation (Output attribute)
The \Temma\Attributes\Check\Output attribute defines a validation contract for output data (template variables). This contract can be used by the view to validate data before sending it to the browser.
use \Temma\Attributes\Check\Output as TµCheckOutput;
class User extends \Temma\Web\Controller {
// the 'name' and 'email' template variables must be present
// and valid; 'balance' is optional
#[TµCheckOutput([
'name' => 'string',
'email' => 'email',
'balance?' => 'float',
])]
public function show() {
// ...
}
}
Unlike other Check\* attributes, Output does not support redirection parameters ($redirect, $redirectVar, $flashVar).
6Programmatic validation (Request)
For cases where attributes are not sufficient (conditional logic, dynamic contracts, etc.), the Request object exposes four validation methods. On failure, they throw a \Temma\Exceptions\Application exception.
6.1validateParams
Validates parameters received in the URL. The contract is an ordered list (one contract per parameter):
// the first parameter must be an integer >= 1,
// the second a slug string
$this->_request->validateParams(['int; min: 1', 'slug']);
// the first must be an integer,
// the rest are accepted as-is
$this->_request->validateParams(['int', '...']);
// using a named contract
$this->_request->validateParams('deleteUserParameters');
6.2validateInput
Validates GET and/or POST parameters. The contract is an associative array (key = parameter name):
// validates 'name' and 'email' parameters (GET or POST)
$this->_request->validateInput([
'name' => 'string; minLen: 2',
'email' => 'email',
]);
// validates POST parameters only, in strict mode;
// 'firstname' is optional, 'age' is forced non-strict
$this->_request->validateInput(
[
'name' => 'string',
'firstname?' => 'string',
'age' => '~int',
],
'POST',
true
);
// using a named contract
$this->_request->validateInput('user');
6.3validatePayload
Validates the request body (payload):
// JSON stream containing a list of integers
$this->_request->validatePayload([
'type' => 'json',
'contract' => 'list; contract: int',
]);
// base64-encoded image (GIF or PNG)
$this->_request->validatePayload('base64; mime: image/gif, image/png');
// using a named contract
$this->_request->validatePayload('avatar');
6.4validateFiles
Validates uploaded files. The contract is an associative array (key = file field name):
// a required JSON file and an optional image
$this->_request->validateFiles([
'definition' => 'json',
'avatar?' => 'binary; mime: image',
]);
// a JSON file and any number of PDF files
$this->_request->validateFiles([
'count' => 'json; contract: int',
'...' => 'binary; mime: application/pdf',
]);
7Programmatic validation (Response)
The Response object allows you to manage a validation contract for output data:
// define an output validation contract
$this->_response->setValidationContract([
'name' => 'string',
'email' => 'email',
]);
// use a named contract
$this->_response->setValidationContract('userData');
// retrieve the defined contract
$contract = $this->_response->getValidationContract();
// remove the contract
$this->_response->setValidationContract(null);
The defined contract can be used by the view to validate template variables before sending them to the browser.