Validation des formulaires

Champs obligatoires

Les champs sont marqués comme obligatoires à l'aide de la méthode setRequired(). Son argument est le texte du message d'erreur qui sera affiché si l'utilisateur ne remplit pas le champ. Si aucun argument n'est fourni, le message d'erreur par défaut est utilisé.

$form->addText('name', 'Nom :')
	->setRequired('Veuillez saisir votre nom.');

Règles

Nous ajoutons des règles de validation aux champs à l'aide de la méthode addRule(). Le premier paramètre est la règle, le deuxième le message d'erreur et le troisième l'argument de la règle de validation.

$form->addPassword('password', 'Mot de passe :')
	->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8);

Les règles de validation ne sont vérifiées que si l'utilisateur a rempli le champ.

Nette est livré avec plusieurs règles prédéfinies dont les noms sont des constantes de la classe Nette\Forms\Form. Nous pouvons appliquer ces règles à tous les champs :

constante description type de l'argument
Required champ obligatoire, alias de setRequired()
Filled champ obligatoire, alias de setRequired()
Blank le champ ne doit pas être rempli
Equal la valeur doit être égale au paramètre mixed
NotEqual la valeur ne doit pas être égale au paramètre mixed
IsIn la valeur doit être l'un des éléments du tableau array
IsNotIn la valeur ne doit être aucun des éléments du tableau array
Valid le champ est-il rempli correctement ? (uniquement dans addConditionOn())

Champs texte

Pour les champs addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat(), certaines des règles suivantes peuvent aussi être appliquées :

MinLength longueur minimale du texte int
MaxLength longueur maximale du texte int
Length longueur dans une plage ou longueur exacte paire [int, int] ou int
Email adresse e-mail valide
URL URL absolue
Pattern correspond à l'expression régulière string
PatternInsensitive comme Pattern, mais insensible à la casse string
Integer valeur entière
Numeric entier non négatif (chiffres seulement)
Float nombre
Min valeur minimale d'un champ numérique int|float
Max valeur maximale d'un champ numérique int|float
Range valeur dans une plage paire [int|float, int|float]

Les règles de validation Integer et Float convertissent automatiquement la valeur en entier ou en nombre à virgule flottante. De plus, la règle URL accepte aussi une adresse sans schéma (par exemple nette.org) et complète le schéma (https://nette.org). L'expression de Pattern et PatternInsensitive doit être valable pour la valeur entière, c'est-à-dire comme si elle était encadrée par les caractères ^ et $.

Nombre d'éléments

Pour les champs addMultiUpload(), addCheckboxList(), addMultiSelect(), vous pouvez aussi utiliser les règles suivantes pour limiter le nombre d'éléments sélectionnés ou de fichiers envoyés :

MinLength nombre minimal int
MaxLength nombre maximal int
Length nombre dans une plage ou nombre exact paire [int, int] ou int

Upload de fichiers

Pour les champs addUpload(), addMultiUpload(), les règles suivantes peuvent aussi être utilisées :

MaxFileSize taille maximale du fichier en octets int
MimeType type MIME, caractères génériques autorisés ('video/*') string|string[]
Image image JPEG, PNG, GIF, WebP, AVIF
Pattern le nom du fichier correspond à l'expression régulière string
PatternInsensitive comme Pattern, mais insensible à la casse string

MimeType et Image nécessitent l'extension PHP fileinfo. Le fait qu'un fichier ou une image soit du type requis est détecté d'après sa signature, et l'intégrité du fichier entier n'est pas vérifiée. Vous pouvez déterminer si une image est endommagée, par exemple, en essayant de la charger.

Messages d'erreur

Toutes les règles prédéfinies, à l'exception de Pattern et PatternInsensitive, ont un message d'erreur par défaut, elles peuvent donc l'omettre. Cependant, en fournissant et en formulant tous les messages personnalisés adaptés à vos besoins, vous rendrez le formulaire plus convivial.

Vous pouvez changer les messages par défaut dans la configuration, en modifiant les textes du tableau Nette\Forms\Validator::$messages, ou à l'aide d'un traducteur.

Les chaînes de substitution suivantes peuvent être utilisées dans le texte des messages d'erreur :

%d remplacé successivement par les arguments de la règle
%n$d remplacé par le n-ième argument de la règle
%label remplacé par le label du champ (sans les deux-points)
%name remplacé par le nom du champ (par exemple name)
%value remplacé par la valeur saisie par l'utilisateur
$form->addText('name', 'Nom :')
	->setRequired('Veuillez remplir %label');

$form->addInteger('id', 'ID :')
	->addRule($form::Range, 'au moins %d et au plus %d', [5, 10]);

$form->addInteger('id', 'ID :')
	->addRule($form::Range, 'au plus %2$d et au moins %1$d', [5, 10]);

Conditions

Outre les règles, il est aussi possible d'ajouter des conditions. Elles s'écrivent de façon semblable aux règles, mais au lieu d'addRule() nous utilisons la méthode addCondition() et, naturellement, nous ne fournissons pas de message d'erreur (la condition ne fait que poser une question) :

$form->addPassword('password', 'Mot de passe :')
	// si la longueur du mot de passe ne dépasse pas 8
	->addCondition($form::MaxLength, 8)
		// alors il doit contenir un chiffre
		->addRule($form::Pattern, 'Doit contenir un chiffre', '.*[0-9].*');

La condition peut être liée à un autre champ que le champ courant à l'aide d'addConditionOn(). Le premier paramètre est une référence au champ. Dans cet exemple, l'e-mail ne sera obligatoire que si la case est cochée (c'est-à-dire si sa valeur est true) :

$form->addCheckbox('newsletters', 'Envoyez-moi les newsletters');

$form->addEmail('email', 'E-mail :')
	// si la case est cochée
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// alors exiger l'e-mail
		->setRequired('Saisissez votre adresse e-mail');

Les conditions peuvent former des structures complexes à l'aide d'elseCondition() et endCondition() :

$form->addText(/* ... */)
	->addCondition(/* ... */) // si la première condition est remplie
		->addConditionOn(/* ... */) // et que la deuxième condition sur un autre champ l'est aussi
			->addRule(/* ... */) // exiger cette règle
		->elseCondition() // si la deuxième condition n'est pas remplie
			->addRule(/* ... */) // exiger ces règles
			->addRule(/* ... */)
		->endCondition() // nous revenons à la première condition
		->addRule(/* ... */);

Le premier argument d'addCondition() peut aussi être une valeur booléenne. C'est utile lorsque la décision est déjà connue au moment de la construction du formulaire, par exemple pour n'appliquer une règle que dans certaines circonstances :

$form->addText('nickname')
	->addCondition($isRequired) // une valeur connue lors de la construction du formulaire
		->setRequired();

Dans Nette, il est très facile de réagir côté JavaScript au fait qu'une condition soit remplie ou non, grâce à la méthode toggle(), voir JavaScript dynamique.

Référence à un autre champ

Vous pouvez aussi passer comme argument d'une règle ou d'une condition un autre champ du formulaire. La règle utilisera alors la valeur saisie plus tard par l'utilisateur dans le navigateur. Cela permet par exemple de valider dynamiquement que le champ password contient la même chaîne que le champ password_confirm :

$form->addPassword('password', 'Mot de passe');
$form->addPassword('password_confirm', 'Confirmez le mot de passe')
    ->addRule($form::Equal, 'Les mots de passe ne correspondent pas', $form['password']);

Règles et conditions personnalisées

Il arrive que les règles de validation intégrées à Nette ne suffisent pas et que nous ayons besoin de valider les données de l'utilisateur à notre façon. Dans Nette, c'est très simple !

Vous pouvez passer n'importe quel callback comme premier paramètre aux méthodes addRule() ou addCondition(). Le callback reçoit le champ lui-même comme premier paramètre et renvoie une valeur booléenne indiquant si la validation a réussi. Lors de l'ajout d'une règle avec addRule(), des arguments supplémentaires peuvent être fournis, qui lui sont ensuite passés comme deuxième paramètre.

Un jeu de validateurs personnalisés peut ainsi être créé sous forme de classe avec des méthodes statiques :

class MyValidators
{
	// teste si la valeur est divisible par l'argument
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// autres validateurs
	}
}

L'utilisation est ensuite très simple :

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'La valeur doit être un multiple de %d',
		8,
	);

Les règles de validation personnalisées peuvent aussi être ajoutées en JavaScript. La condition est que la règle soit une méthode statique. Son nom pour le validateur JavaScript se forme en concaténant le nom de la classe sans les antislashs \, un tiret bas _ et le nom de la méthode. Par exemple, App\MyValidators::validateDivisibility s'écrit AppMyValidators_validateDivisibility et s'ajoute à l'objet Nette.validators :

Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
	return val % args === 0;
};

Événement onValidate

Après la soumission du formulaire, la validation est effectuée : les différentes règles ajoutées par addRule() sont vérifiées, puis l'événement onValidate est déclenché. Son gestionnaire peut servir à une validation supplémentaire, typiquement pour vérifier la bonne combinaison de valeurs de plusieurs champs du formulaire.

Si une erreur est détectée, elle est transmise au formulaire à l'aide de la méthode addError(). Celle-ci peut être appelée soit sur un champ précis, soit directement sur le formulaire.

protected function createComponentSignInForm(): Form
{
	$form = new Form;
	// ...
	$form->onValidate[] = $this->validateSignInForm(...);
	return $form;
}

private function validateSignInForm(Form $form, \stdClass $data): void
{
	if ($data->foo > 1 && $data->bar > 5) {
		$form->addError('Cette combinaison n\'est pas possible.');
	}
}

Erreurs lors du traitement

Dans bien des cas, nous ne découvrons une erreur qu'au moment du traitement d'un formulaire valide, par exemple en écrivant un nouvel enregistrement dans la base de données et en tombant sur une clé dupliquée. Dans ce cas, nous renvoyons là encore l'erreur au formulaire à l'aide de la méthode addError(). Celle-ci peut être appelée soit sur un champ précis, soit directement sur le formulaire :

try {
	$data = $form->getValues();
	$this->user->login($data->username, $data->password);
	$this->redirect('Home:');

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) {
		$form->addError('Mot de passe invalide.');
	}
}

Si possible, nous recommandons d'ajouter l'erreur directement au champ du formulaire, car elle sera alors affichée à côté de lui avec le renderer par défaut.

$form['date']->addError('Désolé, cette date est déjà prise.');

Vous pouvez appeler addError() à plusieurs reprises pour transmettre plusieurs messages d'erreur à un formulaire ou à un champ. Vous les récupérez avec getErrors().

Notez que $form->getErrors() renvoie un récapitulatif de tous les messages d'erreur, y compris ceux transmis directement aux différents champs, et pas seulement ceux transmis directement au formulaire. Les messages d'erreur transmis uniquement au formulaire s'obtiennent via $form->getOwnErrors().

Modifier les valeurs saisies

À l'aide de la méthode addFilter(), nous pouvons modifier la valeur saisie par l'utilisateur. Dans cet exemple, nous tolérerons et supprimerons les espaces dans le code postal :

$form->addText('zip', 'Code postal :')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // supprime les espaces du code postal
	})
	->addRule($form::Pattern, 'Le code postal ne comporte pas cinq chiffres', '\d{5}');

Le filtre s'intègre parmi les règles de validation et les conditions, l'ordre des méthodes a donc de l'importance : le filtre et la règle sont appelés dans le même ordre que celui où les méthodes addFilter() et addRule() sont écrites.

Validation JavaScript

Le langage de formulation des conditions et des règles est très puissant. Toutes les constructions fonctionnent aussi bien côté serveur que côté client en JavaScript. Elles sont transmises dans les attributs HTML data-nette-rules sous forme de JSON. La validation elle-même est assurée par un script qui intercepte l'événement submit du formulaire, parcourt les différents champs et effectue la validation correspondante.

Ce script est netteForms.js et il est disponible depuis plusieurs sources possibles :

Vous pouvez intégrer le script directement dans la page HTML depuis un CDN :

<script src="https://unpkg.com/nette-forms@3"></script>

Ou le copier localement dans le dossier public de votre projet (par exemple depuis vendor/nette/forms/src/assets/netteForms.min.js) :

<script src="/path/to/netteForms.min.js"></script>

Ou l'installer via npm :

npm install nette-forms

Puis le charger et l'exécuter :

import netteForms from 'nette-forms';
netteForms.initOnLoad();

Vous pouvez aussi le charger directement depuis le dossier vendor :

import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();

Vous pouvez désactiver entièrement la validation côté client en ajoutant l'attribut novalidate au formulaire. Le script netteForms.js ne le valide alors pas à la soumission, et la validation n'a donc lieu que sur le serveur :

$form->setHtmlAttribute('novalidate');

JavaScript dynamique

Vous voulez n'afficher les champs d'adresse que si l'utilisateur choisit de se faire envoyer la marchandise par la poste ? Aucun problème. La clé est le couple de méthodes addCondition() & toggle() :

$form->addCheckbox('send_it')
	->addCondition($form::Equal, true)
		->toggle('#address-container');

Ce code dit que, lorsque la condition est remplie (c'est-à-dire lorsque la case est cochée), l'élément HTML #address-container sera visible, et inversement. Nous plaçons donc les champs de formulaire contenant l'adresse du destinataire dans un conteneur portant cet ID, et ils se masqueront ou s'afficheront au clic sur la case. C'est le script netteForms.js qui s'en charge.

N'importe quel sélecteur peut être passé comme argument à la méthode toggle(). Pour des raisons historiques, une chaîne qui commence par une lettre, un chiffre ou un tiret bas et qui ne contient que des lettres, des chiffres, des tirets bas, des traits d'union, des points et des deux-points est traitée comme un ID d'élément, comme si elle était précédée du caractère #. Le deuxième paramètre facultatif permet d'inverser le comportement ; ainsi, si nous écrivions toggle('#address-container', false), l'élément ne serait affiché que si la case n'était pas cochée.

L'implémentation JavaScript par défaut modifie la propriété hidden des éléments. Nous pouvons cependant changer facilement ce comportement, par exemple en ajoutant une animation. Il suffit de redéfinir la méthode Nette.toggle en JavaScript par une solution personnalisée :

Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// masquer ou afficher 'el' selon la valeur de 'visible'
	});
};

Désactiver la validation

Il peut parfois être utile de désactiver la validation. Si l'appui sur un bouton d'envoi ne doit pas déclencher la validation (utile pour les boutons Annuler ou Aperçu), nous la désactivons avec la méthode $submit->setValidationScope([]). S'il ne doit déclencher qu'une validation partielle, nous pouvons indiquer quels champs ou conteneurs du formulaire doivent être validés.

$form->addText('name')
	->setRequired();

$details = $form->addContainer('details');
$details->addInteger('age')
	->setRequired('age');
$details->addInteger('age2')
	->setRequired('age2');

$form->addSubmit('send1'); // Valide tout le formulaire
$form->addSubmit('send2')
	->setValidationScope([]); // Ne valide rien
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Ne valide que le champ 'name'
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Ne valide que le champ 'age'
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // Valide le conteneur 'details'

setValidationScope n'a aucune incidence sur l'Événement onValidate du formulaire, qui sera toujours appelé. L'événement onValidate d'un conteneur ne sera déclenché que si ce conteneur est marqué pour la validation partielle.

La validation partielle influence aussi les valeurs renvoyées par getValues() : le résultat ne contient que les valeurs des champs qui entrent dans le périmètre de validation. Les valeurs des champs hors de ce périmètre sont omises.

version: 4.x