Validation des formulaires

Éléments obligatoires

Nous marquons les éléments obligatoires avec la méthode setRequired(), dont l'argument est le texte du message d'erreur qui s'affiche si l'utilisateur ne remplit pas l'élément. Si nous n'indiquons pas d'argument, le message d'erreur par défaut est utilisé.

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

Règles

Nous ajoutons des règles de validation aux éléments avec la méthode addRule(). Le premier paramètre est la règle, le deuxième est le texte du message d'erreur et le troisième est 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 l'élément.

Nette est livré avec un certain nombre de règles prédéfinies, dont les noms sont des constantes de la classe Nette\Forms\Form. Nous pouvons utiliser ces règles pour tous les éléments :

constante description type d'argument
Required élément obligatoire, alias pour setRequired()
Filled élément obligatoire, alias pour setRequired()
Blank l'élément ne doit pas être rempli
Equal la valeur est égale au paramètre mixed
NotEqual la valeur n'est pas égale au paramètre mixed
IsIn la valeur est égale à l'un des éléments du tableau array
IsNotIn la valeur n'est égale à aucun des éléments du tableau array
Valid l'élément est-il rempli correctement ? (pour conditions)

Entrées textuelles

Pour les éléments addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat(), certaines des règles suivantes peuvent également être utilisé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 alias pour Integer
Float nombre
Min valeur minimale de l'élément numérique int|float
Max valeur maximale de l'élément numérique int|float
Range valeur dans une plage paire [int|float, int|float]

Les règles de validation Integer, Numeric et Float convertissent directement la valeur en entier resp. flottant. De plus, la règle URL accepte également une adresse sans schéma (par ex. nette.org) et complète le schéma (https://nette.org). L'expression dans Pattern et PatternIcase doit s'appliquer à toute la valeur, c'est-à-dire comme si elle était entourée des caractères ^ et $.

Nombre d'éléments

Pour les éléments addMultiUpload(), addCheckboxList(), addMultiSelect(), les règles suivantes peuvent également être utilisées pour limiter le nombre d'éléments sélectionnés resp. de fichiers uploadés :

MinLength nombre minimum int
MaxLength nombre maximum int
Length nombre dans une plage ou nombre exact paire [int, int] ou int

Upload de fichiers

Pour les éléments addUpload(), addMultiUpload(), les règles suivantes peuvent également ê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é sur la base de sa signature et ne vérifie pas l'intégrité de l'ensemble du fichier. On peut vérifier si une image n'est pas 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, il est donc possible de l'omettre. Cependant, en indiquant et en formulant tous les messages sur mesure, vous rendrez le formulaire plus convivial.

Vous pouvez modifier les messages par défaut dans la configuration, en modifiant les textes dans le tableau Nette\Forms\Validator::$messages ou en utilisant un traducteur.

Dans le texte des messages d'erreur, les chaînes de remplacement suivantes peuvent être utilisées :

%d remplace successivement par les arguments de la règle
%n$d remplace par le n-ième argument de la règle
%label remplace par le libellé de l'élément (sans les deux points)
%name remplace par le nom de l'élément (par ex. name)
%value remplace 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

En plus des règles, il est également possible d'ajouter des conditions. Elles s'écrivent de la même manière que les règles, sauf qu'au lieu de addRule(), nous utilisons la méthode addCondition() et, bien sûr, nous n'indiquons aucun message d'erreur (la condition ne fait que demander) :

$form->addPassword('password', 'Mot de passe:')
	// si le mot de passe n'est pas plus long que 8 caractères
	->addCondition($form::MaxLength, 8)
		// alors il doit contenir un chiffre
		->addRule($form::Pattern, 'Doit contenir un chiffre', '.*[0-9].*');

La condition peut également être liée à un autre élément que l'élément actuel à l'aide de addConditionOn(). Comme premier paramètre, nous indiquons une référence à l'élément. Dans cet exemple, l'e-mail ne sera obligatoire que si la case à cocher est cochée (sa valeur sera true) :

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

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

Il est possible de créer des structures complexes à partir de conditions à l'aide de elseCondition() et endCondition() :

$form->addText(/* ... */)
	->addCondition(/* ... */) // si la première condition est remplie
		->addConditionOn(/* ... */) // et la deuxième condition sur un autre élément
			->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(/* ... */);

Dans Nette, il est très facile de réagir à la satisfaction ou non d'une condition également côté JavaScript à l'aide de la méthode toggle(), voir javascript dynamique.

Référence à un autre élément

Comme argument de règle ou de condition, il est également possible de passer un autre élément du formulaire. La règle utilisera alors la valeur saisie ultérieurement par l'utilisateur dans le navigateur. Ainsi, il est possible, par exemple, de valider dynamiquement que l'élément password contient la même chaîne que l'élément password_confirm :

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

Règles et conditions personnalisées

Parfois, nous nous trouvons dans une situation où les règles de validation intégrées dans Nette ne suffisent pas et nous devons valider les données de l'utilisateur à notre manière. Dans Nette, c'est très simple !

Aux méthodes addRule() ou addCondition(), il est possible de passer n'importe quel callback comme premier paramètre. Celui-ci reçoit comme premier paramètre l'élément lui-même et retourne une valeur booléenne indiquant si la validation s'est déroulée correctement. Lors de l'ajout d'une règle à l'aide de addRule(), il est possible de spécifier d'autres arguments, qui sont ensuite passés comme deuxième paramètre.

Nous pouvons ainsi créer notre propre ensemble de validateurs sous forme de classe avec des méthodes statiques :

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

	public static function validateEmailDomain(Nette\Forms\Control $input, $domain)
	{
		// autres validateurs
	}
}

L'utilisation est alors très simple :

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

Il est également possible d'ajouter des règles de validation personnalisées à JavaScript. La condition est que la règle soit une méthode statique. Son nom pour le validateur JavaScript est formé en joignant le nom de la classe sans les barres obliques inverses \, le trait de soulignement _ et le nom de la méthode. Par exemple, App\MyValidators::validateDivisibility s'écrira AppMyValidators_validateDivisibility et sera ajouté à l'objet Nette.validators:

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

Événement onValidate

Après la soumission du formulaire, une validation est effectuée, au cours de laquelle les règles individuelles ajoutées à l'aide de addRule() sont vérifiées, puis l'événement onValidate est déclenché. Son gestionnaire peut être utilisé pour une validation supplémentaire, typiquement pour vérifier la combinaison correcte de valeurs dans plusieurs éléments du formulaire.

Si une erreur est détectée, nous la transmettons au formulaire à l'aide de la méthode addError(). Celle-ci peut être appelée soit sur un élément spécifique, soit directement sur le formulaire.

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

public 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 de nombreux cas, nous ne découvrons une erreur qu'au moment où nous traitons un formulaire valide, par exemple lors de l'écriture d'un nouvel élément dans la base de données et que nous rencontrons une duplication de clés. Dans ce cas, nous transmettons à nouveau l'erreur au formulaire à l'aide de la méthode addError(). Celle-ci peut être appelée soit sur un élément spécifique, 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'attacher l'erreur directement à l'élément du formulaire, car elle s'affichera à côté de lui lors de l'utilisation du moteur de rendu par défaut.

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

Vous pouvez appeler addError() à plusieurs reprises et ainsi transmettre plusieurs messages d'erreur au formulaire ou à l'élément. Vous les obtenez à l'aide de getErrors().

Attention, $form->getErrors() retourne un résumé de tous les messages d'erreur, y compris ceux qui ont été transmis directement aux éléments individuels, et pas seulement directement au formulaire. Les messages d'erreur transmis uniquement au formulaire sont obtenus via $form->getOwnErrors().

Modification de l'entrée

À 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); // nous supprimons les espaces du code postal
	})
	->addRule($form::Pattern, 'Le code postal n\'est pas sous la forme de cinq chiffres', '\d{5}');

Le filtre s'insère entre les règles de validation et les conditions, et l'ordre des méthodes est donc important, c'est-à-dire que le filtre et la règle sont appelés dans l'ordre où les méthodes addFilter() et addRule() sont placées.

Validation JavaScript

Le langage pour formuler les conditions et les règles est très puissant. Toutes les constructions fonctionnent à la fois côté serveur et côté JavaScript. Elles sont transmises dans les attributs HTML data-nette-rules sous forme de JSON. La validation elle-même est ensuite effectuée par un script qui intercepte l'événement submit du formulaire, parcourt les éléments individuels et effectue la validation appropriée.

Ce script est netteForms.js et est disponible à partir de plusieurs sources possibles :

Vous pouvez insérer 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 du projet (par ex. 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

Et ensuite le charger et l'exécuter :

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

Alternativement, vous pouvez le charger directement depuis le dossier vendor :

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

JavaScript dynamique

Voulez-vous afficher les champs pour saisir l'adresse uniquement si l'utilisateur choisit d'envoyer la marchandise par la poste ? Aucun problème. La clé est la paire 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 à cocher est cochée, l'élément HTML #address-container sera visible. Et inversement. Nous plaçons donc les éléments du formulaire avec l'adresse du destinataire dans un conteneur avec cet ID, et lors du clic sur la case à cocher, ils se masquent ou s'affichent. C'est le script netteForms.js qui s'en charge.

Comme argument de la méthode toggle(), il est possible de passer n'importe quel sélecteur. Pour des raisons historiques, une chaîne alphanumérique sans autres caractères spéciaux est comprise comme l'ID de l'élément, c'est-à-dire comme si elle était précédée du caractère #. Le deuxième paramètre facultatif permet d'inverser le comportement, c'est-à-dire que si nous utilisions toggle('#address-container', false), l'élément ne s'afficherait au contraire que si la case à cocher n'était pas cochée.

L'implémentation par défaut en JavaScript modifie la propriété hidden des éléments. Cependant, nous pouvons facilement changer le comportement, par exemple en ajoutant une animation. Il suffit de remplacer la méthode Nette.toggle en JavaScript par notre propre solution :

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

Désactivation de la validation

Parfois, il peut être utile de désactiver la validation. Si l'appui sur le bouton d'envoi ne doit pas effectuer de validation (approprié pour les boutons Annuler ou Aperçu), nous la désactivons avec la méthode $submit->setValidationScope([]). S'il doit effectuer une validation partielle, nous pouvons spécifier quels champs ou conteneurs de 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 du tout
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Valide uniquement l'élément name
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Valide uniquement l'élément age
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // Valide le conteneur details

setValidationScope n'affecte pas l'événement onValidate du formulaire, qui sera toujours appelée. L'événement onValidate du conteneur ne sera déclenché que si ce conteneur est marqué pour une validation partielle.

version: 4.0