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.