Validation des données
1Introduction
Temma propose un système complet de validation des données, à la fois en entrée (données reçues par le serveur) et en sortie (données envoyées au navigateur).
Trois niveaux d'utilisation sont disponibles :
- Les attributs PHP (approche déclarative) : les attributs Check\* permettent de valider les données directement sur les contrôleurs et les actions, sans écrire de code de validation. C'est l'approche la plus simple et la plus courante.
- Les méthodes de l'objet Request (approche programmatique) : pour les cas où les attributs ne suffisent pas, les méthodes validate*() de l'objet Request offrent un contrôle total depuis le code du contrôleur.
- L'objet DataFilter (bas niveau) : la brique fondamentale sur laquelle repose tout le système. Peut être utilisé partout dans votre code pour valider n'importe quelle donnée.
Le fil rouge de tout le système est le contrat de validation : une définition qui décrit le format attendu des données.
2Contrats de validation
2.1Format des contrats
Un contrat de validation peut s'écrire sous deux formes :
Format chaîne — les paramètres sont séparés par des points-virgules :
// un entier entre 0 et 100
'int; min: 0; max: 100'
// une chaîne de 2 à 50 caractères
'string; minLen: 2; maxLen: 50'
// une adresse email correspondant à un masque
'email; mask: @mondomaine\.com$'
Format tableau — chaque paramètre est une clé du tableau :
// un entier entre 0 et 100
['type' => 'int', 'min' => 0, 'max' => 100]
// un tableau associatif avec des clés typées
[
'type' => 'assoc',
'keys' => [
'id' => 'int',
'nom' => 'string; minLen: 2',
'email' => 'email',
]
]
Les deux formats sont interchangeables et peuvent être utilisés partout où un contrat est attendu. Pour le détail exhaustif, voir la documentation de l'objet DataFilter.
2.2Mode strict et non-strict
Par défaut, la validation est non-stricte : les données sont converties si possible (une chaîne "42" est acceptée pour un contrat int), les valeurs hors limites sont ajustées (un nombre trop grand est ramené au maximum), et les chaînes trop longues sont tronquées.
En mode strict, aucune conversion n'est effectuée : les données doivent correspondre exactement au type attendu, et tout dépassement de limites provoque une erreur.
Le mode peut être contrôlé de plusieurs façons :
- Globalement, via le paramètre $strict des attributs ou des méthodes de validation.
- Par contrat, en préfixant le type : = pour forcer le mode strict (ex. '=int'), ~ pour forcer le mode non-strict (ex. '~string').
2.3Types disponibles
Temma propose un large éventail de types de validation :
- Scalaires : null, bool, true, false, int, float, string
- Texte et identifiants : email, url, slug, uuid, color, phone
- Dates et heures : date, time, datetime
- Réseau : ip, ipv4, ipv6, mac, port
- Codes : isbn, ean, hash (et alias md5, sha1, sha256, sha512)
- Géographie : geo
- Structures : enum, list, assoc
- Données binaires : json, binary, base64
Les types peuvent être combinés avec le pipe : 'null|int|string', et rendus nullables avec le préfixe ? : '?bool'.
Pour le détail complet de chaque type et de ses paramètres, voir la documentation de l'objet DataFilter.
2.4Paramètres universels
Plusieurs paramètres sont disponibles pour tous les types (ou presque) :
- default : valeur par défaut si la donnée est invalide.
- min / max : valeurs minimale et maximale (nombres, dates, coordonnées).
- minLen / maxLen : longueur minimale et maximale (chaînes, listes). maxLen accepte les unités K, M, G.
- mask : expression régulière de validation (chaînes, emails, URLs).
- values : valeurs acceptées (pour enum).
- contract : contrat de validation pour les éléments d'une list ou d'un json.
- keys : clés attendues dans un tableau assoc.
- mime : types MIME autorisés (binary, base64).
- charset : encodage de caractères.
- format / inFormat / outFormat : formats de date/heure.
2.5Clés optionnelles et wildcard
Dans les contrats de type assoc (et dans les paramètres GET/POST), les clés peuvent être marquées comme optionnelles avec le suffixe ? :
[
'nom' => 'string', // obligatoire
'prenom?' => 'string', // optionnel
'age' => 'int; min: 0', // obligatoire
]
Le wildcard ... permet d'accepter des données supplémentaires non définies dans le contrat. Il peut optionnellement forcer un type :
// accepte toutes les clés supplémentaires telles quelles
['nom' => 'string', '...']
// accepte les clés supplémentaires, mais elles doivent être des entiers
['nom' => 'string', '...' => 'int']
Sans wildcard, en mode non-strict les clés non définies sont supprimées ; en mode strict, elles provoquent une erreur.
3Contrats nommés (configuration)
Pour éviter de dupliquer les contrats de validation, il est possible de les définir une fois dans le fichier de configuration etc/temma.php, sous la clé validationTypes :
<?php
return [
'validationTypes' => [
// contrat simple (énumération)
'sitecolor' => 'enum; values: orange, yellow, lime, green',
// contrat complexe (tableau associatif)
'user' => [
'type' => 'assoc',
'keys' => [
'id?' => 'int',
'login' => 'string',
'email' => 'email',
],
],
// objet de validation personnalisé
'category' => '\App\Validations\CategoryValidator',
],
];
Ces contrats nommés peuvent ensuite être utilisés partout où un contrat est attendu (attributs Check\*, méthodes validate*(), objet DataFilter), simplement en passant leur nom sous forme de chaîne :
// dans un attribut
#[TµCheckPost('user')]
// dans une méthode de validation
$this->_request->validateInput('user');
// dans DataFilter
\Temma\Utils\DataFilter::process($data, 'user');
4Validation en entrée (attributs Check)
4.1Les cinq attributs
Les attributs Check\* permettent de valider les données entrantes de manière déclarative, directement sur les contrôleurs et les actions :
- \Temma\Attributes\Check\Params : paramètres présents dans l'URL.
- \Temma\Attributes\Check\Get : paramètres GET.
- \Temma\Attributes\Check\Post : paramètres POST.
- \Temma\Attributes\Check\Files : fichiers uploadés.
- \Temma\Attributes\Check\Payload : corps de la requête (JSON, base64, binaire).
Exemple : valider qu'un formulaire POST contient un email et un nom :
use \Temma\Attributes\Check\Post as TµCheckPost;
class User extends \Temma\Web\Controller {
// en cas d'erreur, redirige vers le referer HTTP ;
// les données POST reçues sont copiées dans la variable
// flash par défaut '__form'
#[TµCheckPost([
'email' => 'email',
'nom' => 'string; minLen: 2',
])]
public function create() {
// les données sont valides, on peut les utiliser
}
}
Exemple : valider les paramètres d'URL d'une action :
use \Temma\Attributes\Check\Params as TµCheckParams;
class Article extends \Temma\Web\Controller {
// attend un entier positif et une chaîne (slug)
#[TµCheckParams(['int; min: 1', 'slug'])]
public function show(int $id, string $slug) {
// ...
}
}
Exemple : valider un payload JSON :
use \Temma\Attributes\Check\Payload as TµCheckPayload;
class Api extends \Temma\Web\Controller {
// attend un flux JSON contenant un tableau associatif ;
// en cas d'erreur, redirige vers '/api/error' ;
// la variable flash '__apiErr' est mise à true
#[TµCheckPayload(
[
'type' => 'json',
'contract' => [
'type' => 'assoc',
'keys' => [
'id' => 'int',
'role' => 'enum; values: user, admin',
],
],
],
redirect: '/api/error',
flashVar: 'apiErr',
)]
public function update() {
// ...
}
}
Pour les détails complets et d'autres exemples, voir la documentation des attributs Check.
4.2Paramètres communs
Tous les attributs Check\* (sauf Output) acceptent des paramètres communs pour contrôler le comportement en cas d'échec de validation :
- $strict : (bool) active le mode de validation stricte (false par défaut).
- $redirect : (string) URL de redirection en cas d'erreur.
- $redirectVar : (string) nom de la variable de template contenant l'URL de redirection.
- $redirectReferer : (bool) true par défaut. Si true et que $redirect et $redirectVar sont vides, l'en-tête HTTP REFERER est utilisé comme URL de redirection.
- $flashVar : (string) nom de la variable flash qui contiendra les données reçues en cas de redirection ('form' par défaut, ce qui rend les données accessibles dans __form). Mettre à null pour désactiver.
4.3Priorité de redirection
Lorsque les données ne sont pas valides, l'URL de redirection est déterminée dans l'ordre suivant :
- Le paramètre $redirect, s'il est défini.
- Le contenu de la variable de template désignée par $redirectVar, si elle existe et est non vide.
- L'en-tête HTTP Referer, si $redirectReferer est à true et que l'en-tête est présent.
- La clé redirect de la configuration étendue x-security dans etc/temma.php.
Si aucune URL n'est trouvée, une erreur HTTP 403 (Forbidden) est retournée.
5Validation en sortie (attribut Output)
L'attribut \Temma\Attributes\Check\Output permet de définir un contrat de validation pour les données de sortie (variables de template). Ce contrat peut être utilisé par la vue pour valider les données avant de les envoyer au navigateur.
use \Temma\Attributes\Check\Output as TµCheckOutput;
class User extends \Temma\Web\Controller {
// les variables de template 'name' et 'email' doivent être présentes
// et valides ; 'balance' est optionnelle
#[TµCheckOutput([
'name' => 'string',
'email' => 'email',
'balance?' => 'float',
])]
public function show() {
// ...
}
}
Contrairement aux autres attributs Check\*, Output ne prend pas en charge les paramètres de redirection ($redirect, $redirectVar, $flashVar).
6Validation programmatique (Request)
Pour les cas où les attributs ne suffisent pas (logique conditionnelle, contrat dynamique, etc.), l'objet Request expose quatre méthodes de validation. En cas d'échec, elles lèvent une exception \Temma\Exceptions\Application.
6.1validateParams
Valide les paramètres reçus dans l'URL. Le contrat est une liste ordonnée (un contrat par paramètre) :
// le premier paramètre doit être un entier >= 1,
// le second une chaîne de type slug
$this->_request->validateParams(['int; min: 1', 'slug']);
// le premier doit être un entier,
// les suivants sont acceptés tels quels
$this->_request->validateParams(['int', '...']);
// utilisation d'un contrat nommé
$this->_request->validateParams('deleteUserParameters');
6.2validateInput
Valide les paramètres GET et/ou POST. Le contrat est un tableau associatif (clé = nom du paramètre) :
// valide les paramètres 'nom' et 'email' (GET ou POST)
$this->_request->validateInput([
'nom' => 'string; minLen: 2',
'email' => 'email',
]);
// valide uniquement les paramètres POST, en mode strict ;
// 'prenom' est optionnel, 'age' est forcé en non-strict
$this->_request->validateInput(
[
'nom' => 'string',
'prenom?' => 'string',
'age' => '~int',
],
'POST',
true
);
// utilisation d'un contrat nommé
$this->_request->validateInput('user');
6.3validatePayload
Valide le corps de la requête (payload) :
// flux JSON contenant une liste d'entiers
$this->_request->validatePayload([
'type' => 'json',
'contract' => 'list; contract: int',
]);
// image encodée en base 64 (GIF ou PNG)
$this->_request->validatePayload('base64; mime: image/gif, image/png');
// utilisation d'un contrat nommé
$this->_request->validatePayload('avatar');
6.4validateFiles
Valide les fichiers uploadés. Le contrat est un tableau associatif (clé = nom du champ de fichier) :
// un fichier JSON requis et une image optionnelle
$this->_request->validateFiles([
'definition' => 'json',
'avatar?' => 'binary; mime: image',
]);
// un fichier JSON et n'importe quel nombre de fichiers PDF
$this->_request->validateFiles([
'nombre' => 'json; contract: int',
'...' => 'binary; mime: application/pdf',
]);
7Validation programmatique (Response)
L'objet Response permet de gérer un contrat de validation pour les données de sortie :
// définir un contrat de validation de sortie
$this->_response->setValidationContract([
'name' => 'string',
'email' => 'email',
]);
// utiliser un contrat nommé
$this->_response->setValidationContract('userData');
// récupérer le contrat défini
$contract = $this->_response->getValidationContract();
// supprimer le contrat
$this->_response->setValidationContract(null);
Le contrat défini pourra être utilisé par la vue pour valider les variables de template avant de les envoyer au navigateur.