Thème
Validation Frontend (Symfony Constraints)
vue-form intègre un moteur de validation côté client puissant et autonome, conçu pour répliquer le comportement du composant Validator de Symfony. Ce moteur permet de valider les données saisies par l'utilisateur en temps réel, sans avoir à soumettre le formulaire au serveur pour les vérifications de base.
Sommaire
- Fonctionnement Général
- Structure JSON (Côté Symfony)
- Contraintes Supportées
- La Contrainte
Callback - Champs Conditionnels et Validation
- Validation Globale et Blocage d'Envoi
- Enregistrer des Contraintes Personnalisées
- Utilisation Autonome (Hors Formulaire)
Fonctionnement Général
Le backend Symfony sérialise les contraintes appliquées aux champs dans un format JSON, qui est envoyé au frontend via la propriété form.vars.constraints.
Le moteur de validation front-end :
- Écoute les changements : Un composable Vue (
useValidation) surveille les saisies de l'utilisateur avec un petit délai (debounce de 400ms) pour ne pas être trop intrusif, et réagit instantanément à la perte de focus (blur/focusout). - Exécute les règles : Les contraintes JSON sont hydratées sous forme de classes TS et évaluées.
- Affiche les erreurs : Les erreurs frontend sont fusionnées avec les erreurs existantes du backend (provenant de
form.vars.errors) et affichées via le composant<form-errors>.
Contraintes non évaluables (mode strict)
Une contrainte que le client ne sait pas évaluer ne donne jamais un faux succès. C'est le cas d'une contrainte sans type, d'un type non enregistré (ex. Iban) ou d'une contrainte qui lève une exception (ex. motif Regex PCRE non supporté par JavaScript comme (?i)). Par défaut, le champ reçoit alors une violation générique (This value could not be verified.), une seule par champ, et l'envoi est bloqué.
Pour laisser le serveur trancher, désactivez le mode strict. Les contraintes non évaluables sont alors ignorées et seul un message console est émis :
typescript
import { defaultValidator } from '@codifyo/form-vue/src/validation';
defaultValidator.setStrict(false);
// ou garder le mode strict avec un message traduit :
defaultValidator.setStrict(true, 'Cette valeur n\'a pas pu être vérifiée.');Structure JSON (Côté Symfony)
Pour que la validation front-end fonctionne, le backend doit envoyer un tableau d'objets définissant le type de contrainte et ses options. La structure imite parfaitement les classes Symfony correspondantes.
json
"constraints": [
{
"type": "NotBlank",
"message": "Veuillez renseigner ce champ."
},
{
"type": "Length",
"min": 5,
"max": 50,
"minMessage": "Le texte doit faire au moins {{ limit }} caractères.",
"maxMessage": "Le texte ne doit pas dépasser {{ limit }} caractères."
}
]Contraintes Supportées
La majorité des contraintes natives de Symfony sont supportées :
Basic
NotBlank/BlankNotNull/IsNullIsTrue/IsFalse
String
Length(options:min,max,minMessage,maxMessage,exactMessage)EmailRegex(options:pattern,match)Url(options:protocols,requireTld,tldMessage)
Number
Range(options:min,max,notInRangeMessage,minMessage,maxMessage)Positive/PositiveOrZeroNegative
Comparison
Les contraintes de comparaison permettent d'évaluer une valeur par rapport à une valeur cible fixe (value) ou dynamiquement par rapport à un autre champ du formulaire (propertyPath) :
EqualTo/NotEqualToIdenticalTo/NotIdenticalToLessThan/LessThanOrEqualGreaterThan/GreaterThanOrEqual
Elles prennent en charge :
- Les nombres : comparaison arithmétique standard.
- Les chaînes de caractères : comparaison lexicographique.
- Les dates et datetimes : comparaison chronologique (supporte les instances
Date, les chaînesYYYY-MM-DD,YYYY-MM-DD HH:mm:sset ISO).
Navigation avec propertyPath
Il est possible de comparer la valeur d'un champ avec n'importe quel autre champ de l'arborescence de formulaire en utilisant la syntaxe native Symfony Form :
parent.all[autreChamp].dataparent.children[autreChamp].dataparent[autreChamp].data- Chaînage d'accès complexes :
parent.all[adresse].all[ville].dataouparent.parent...
Lorsqu'un champ surveille un autre champ via propertyPath, sa validation se réexécute automatiquement et réactivement lorsque le champ cible est modifié.
Comparaison de dates & Parité stricte avec Symfony PHP
Le moteur de validation applique une parité stricte avec le comportement de Symfony PHP (AbstractComparisonValidator) :
- Champs
DateType(YYYY-MM-DD) : En Symfony PHP,DateTyperéinitialise systématiquement l'heure à minuit (!Y-m-d). Côté front, toute valeur de date pureYYYY-MM-DDest ainsi instanciée à00:00:00.000en fuseau horaire local. - Comparaison basée sur les timestamps exacts : Aucune troncature artificielle n'est appliquée, reproduisant exactement la comparaison d'objets
\DateTimeInterfacede PHP :- Deux dates au format
YYYY-MM-DDouYYYY-MM-DD 00:00:00(ex: contrainte'today') possèdent toutes les deux l'heure00:00:00et sont donc strictement égales. Une contrainteGreaterThanrejettera l'égalité (violation), tandis queGreaterThanOrEquall'acceptera. - Si la valeur cible fournie par le backend contient une heure non nulle (ex:
14:30:00provenant par inadvertance d'unnew \DateTime()), cette heure est prise en compte dans la comparaison exactement comme le fait PHP ($date > $comparedDate). Bonne pratique Symfony : Pour contraindre un champDateTypepar rapport à la date du jour, privilégiez'today'plutôt qu'une instancenew \DateTime()avec l'heure courante.
- Deux dates au format
Date
Date(Format YYYY-MM-DD)DateTime
File
FileetImage(options:maxSize,mimeTypes,maxSizeMessage,mimeTypesMessage)- Remarque :
maxSizeaccepte les chaînes au format Symfony (ex:"2M","1024k","2.5MiB") ou les octets bruts.
- Remarque :
Collection
Choice(options:choicessous forme de tableau,multiple)Count(options:min,max)All(options:constraintstableau de contraintes appliquées à chaque élément)
La Contrainte Callback
La contrainte personnalisée Callback est extrêmement puissante, car elle permet d'exécuter n'importe quelle logique JavaScript à la volée.
Structure depuis Symfony :
json
{
"type": "Callback",
"callback": "if (value === 'interdit') { return 'Ce mot est banni.'; } return true;"
}La fonction JavaScript reçoit deux arguments :
value: La valeur actuelle du champ.context: Un objetValidationContextcontenant :rootModel: L'état réactif complet du formulaire (v-model).propertyPath: Le nom / chemin complet du champ.isVisible: Un booléen indiquant si le champ est actuellement visible dans l'interface (selondata-conditionet les conteneurs parents).validator: L'instance du validateur (Validator), permettant d'exécuter d'autres contraintes à la demande.
Exemples d'utilisation de Callback :
1. Valider en fonction d'un autre champ :
javascript
"if (context.rootModel.age < 18 && value === 'alcool') return 'Interdit aux mineurs !'; return true;"2. Vérifier la visibilité du champ :
javascript
"if (!context.isVisible) return true; return value ? true : 'Champ requis lorsqu\'il est affiché';"3. Chaîner d'autres contraintes via context.validator :
javascript
"const violations = context.validator.validate(value, [{ type: 'NotBlank' }, { type: 'Email' }], context.rootModel, context.propertyPath); return violations.length > 0 ? violations.map(v => v.message) : true;"Valeurs de retour acceptées par le callback :
true: La valeur est valide.false: La valeur est invalide (un message générique s'affichera).string: La valeur est invalide, la string est utilisée comme message d'erreur.string[]: Renvoie plusieurs messages d'erreurs en même temps.
Champs Conditionnels et Validation
Lorsqu'un champ possède un attribut data-condition (voir la documentation dédiée) et qu'il est masqué :
- Toutes les validations du champ sont ignorées (y compris
NotBlank), et le champ ne bloque pas la soumission du formulaire. - Si le champ était en erreur avant d'être masqué, ses erreurs sont automatiquement effacées.
- Dès que le champ redevient visible, les validations s'appliquent à nouveau normalement.
Validation Globale et Blocage d'Envoi
Par défaut, les champs se valident en temps réel (à la frappe et à la perte de focus). Cependant, si vous avez besoin de bloquer l'envoi d'un formulaire qui contient des erreurs, le composant <symfony-form> met à disposition une vérification globale.
1. Lors de la soumission (@submit)
L'événement @submit émet un payload contenant la propriété isValid. Le composant s'occupe de valider l'ensemble de ses champs avant d'émettre cet événement :
vue
<template>
<symfony-form :form="form" v-model="model" @submit="handleSubmit" />
</template>
<script setup>
function handleSubmit({ value, event, isValid }) {
if (!isValid) {
// Le formulaire contient des erreurs !
// L'envoi est bloqué côté front et les champs en erreur sont mis en surbrillance.
return;
}
// Tout est valide, envoi à l'API...
}
</script>2. Validation Manuelle via ref
Vous pouvez aussi forcer la validation globale à tout moment via la méthode exposée validate() :
vue
<template>
<symfony-form ref="myFormRef" :form="form" v-model="model" />
<button type="button" @click="checkAndNext">Étape suivante</button>
</template>
<script setup>
import { ref } from 'vue';
const myFormRef = ref(null);
function checkAndNext() {
// force la validation de l'arbre et renvoie true/false
const isValid = myFormRef.value.validate();
if (isValid) {
// passe à l'étape suivante...
}
}
</script>Enregistrer des Contraintes Personnalisées
Si tu as créé des contraintes PHP spécifiques à ton application métier (ex: ValidSiret), tu peux créer son équivalent TypeScript et l'enregistrer dans le validateur.
Cette opération peut se faire n'importe où dans ton application (par ex. main.ts).
1. Créer la classe de contrainte
typescript
import { Constraint, ConstraintViolation, ValidationContext } from '@codifyo/form-vue/src/validation';
export class ValidSiret extends Constraint {
// La méthode validate doit retourner un tableau de ConstraintViolation
validate(value: any, context: ValidationContext): ConstraintViolation[] {
// Ignorer si vide (laisser NotBlank s'en charger)
if (this.isEmpty(value)) return [];
// Logique métier...
const isSiretValid = /^[0-9]{14}$/.test(String(value));
if (!isSiretValid) {
// this.createViolation génère l'objet d'erreur et remplace {{ var }} si besoin
return [
this.createViolation(
this.options.message || 'Le SIRET doit contenir exactement 14 chiffres.',
{},
context,
value
)
];
}
return [];
}
}2. Enregistrer la contrainte
typescript
import { defaultValidator } from '@codifyo/form-vue/src/validation';
import { ValidSiret } from './MesContraintes/ValidSiret';
defaultValidator.registerConstraint('ValidSiret', ValidSiret);Désormais, si le composant reçoit :
json
{ "type": "ValidSiret", "message": "Numéro SIRET invalide !" }La validation s'exécutera automatiquement avec ta classe locale !
Utilisation Autonome (Hors Formulaire)
Le moteur de validation n'est pas strictement couplé aux formulaires Vue. Tu peux l'utiliser pour valider n'importe quelle donnée :
typescript
import { defaultValidator } from '@codifyo/form-vue/src/validation';
const maDonnee = "test@";
const mesContraintes = [
{ type: "NotBlank" },
{ type: "Email", message: "Email mal formatté." }
];
const erreurs = defaultValidator.validate(maDonnee, mesContraintes);
if (erreurs.length > 0) {
console.log(erreurs.map(e => e.message));
// Affiche : ["Email mal formatté."]
}