Ajouter une bibliothèque de validation à un projet de trois formulaires revient à installer une dépendance, à en suivre les mises à jour et à en apprendre la syntaxe pour un besoin que le langage couvre déjà. PHP dispose de fonctions de validation des données capables de traiter la quasi totalité des cas courants, et le reste relève de contraintes métier qu'aucune bibliothèque ne connaîtra jamais à votre place. Ce qui manque le plus souvent n'est pas l'outil mais l'organisation : une validation écrite au fil des besoins, dispersée entre le contrôleur et le gabarit, devient impossible à relire et laisse passer précisément ce qu'elle devait arrêter.
Pourquoi la validation serveur reste indispensable
Le sujet paraît entendu et il ne l'est pas, tant les contrôles effectués dans le navigateur donnent un sentiment de sécurité. Cette question s'inscrit dans le panorama que nous dressons à propos des failles de sécurité d'un site Internet.
Le navigateur ne protège rien
Les attributs de validation des champs et les contrôles écrits en JavaScript améliorent l'expérience de saisie et n'ont aucune valeur de sécurité. N'importe qui peut envoyer une requête directement, sans passer par le formulaire, avec les valeurs de son choix. Il ne s'agit même pas d'une manœuvre sophistiquée : un outil en ligne de commande suffit. Toute donnée arrivant sur le serveur doit donc être considérée comme arbitraire, y compris celle provenant d'un champ caché, d'une liste déroulante ou d'un champ en lecture seule, qui ne sont que des indications d'affichage. Le prix d'un article envoyé dans un champ caché est l'exemple canonique : il paraît maîtrisé puisque le formulaire l'écrit lui même, et il est en réalité entièrement sous le contrôle de celui qui envoie la requête.
Valider n'est pas nettoyer
Deux opérations distinctes sont régulièrement confondues. Valider consiste à décider si une donnée est acceptable et à refuser le traitement si elle ne l'est pas. Nettoyer consiste à transformer une donnée pour la rendre acceptable, en retirant des caractères par exemple. Le nettoyage silencieux est dangereux : il peut transformer une saisie en une autre valeur parfaitement valide mais différente de ce que l'utilisateur voulait, ce qui produit des enregistrements erronés que personne ne détecte. La règle raisonnable consiste à valider et refuser, en réservant le nettoyage aux espaces superflus. Le retrait des espaces en début et en fin de saisie est en effet sans risque et évite une grande partie des refus qui exaspèrent les utilisateurs sans rien apporter.
Valider n'est pas échapper
Une donnée valide reste dangereuse à l'affichage si elle n'est pas traitée selon son contexte de sortie. Un nom de famille contenant une apostrophe est parfaitement légitime et cassera une page mal écrite. Les deux opérations interviennent à des moments différents du traitement, la validation à la réception et l'échappement à la sortie, sujet que nous détaillons dans notre article sur la manière d'échapper correctement ses sorties en PHP. Les confondre conduit soit à des données mutilées en base, soit à des pages vulnérables, deux défauts qui se corrigent difficilement une fois le site en production.
Le typage ne suffit pas
Les données reçues sont toujours des chaînes de caractères, y compris celles qui représentent des nombres ou des dates. Une conversion de type accepte silencieusement des valeurs surprenantes, une chaîne commençant par des chiffres devenant un nombre valide dans certains contextes. La conversion doit donc être précédée d'une vérification de format, et non l'inverse. C'est une source d'erreurs discrète, qui produit des enregistrements avec des valeurs nulles ou aberrantes sans qu'aucune exception ne soit levée nulle part. La vérification préalable du format, puis la conversion explicite, est la seule séquence qui donne un résultat prévisible dans tous les cas.
Les champs absents
Un champ non coché, un champ vidé ou un champ simplement absent de la requête ne se présentent pas de la même façon. Une case à cocher non cochée n'est pas envoyée du tout, ce qui produit une erreur si le code suppose sa présence. La distinction entre absent, vide et invalide doit être posée explicitement pour chaque champ, et la valeur par défaut attribuée aux champs absents doit être un choix conscient plutôt que le résultat du hasard de l'implémentation. Une déclaration listant tous les champs attendus, avec leur valeur par défaut, rend cette question triviale et supprime définitivement les avertissements sur les clés inexistantes.

Les briques natives suffisantes
PHP fournit un jeu de fonctions dédiées à ce travail, souvent méconnues parce qu'elles portent des noms peu explicites et se configurent par des constantes. Elles couvrent l'essentiel des besoins.
Les filtres de validation
La famille de fonctions de filtrage propose des validateurs pour les entiers, les nombres décimaux, les booléens, les adresses électroniques, les adresses réseau et les adresses web. Ces validateurs retournent la valeur convertie ou une valeur d'échec, ce qui permet de valider et de convertir en une seule opération. Ils acceptent des options, notamment des bornes pour les entiers, ce qui couvre directement une grande partie des contraintes numériques sans écrire la moindre condition. Il faut simplement prendre garde à la valeur retournée en cas d'échec, qui peut être confondue avec une valeur légitime lorsque le champ accepte zéro ou une chaîne vide.
Les expressions régulières
Pour les formats propres à un métier, code postal, numéro de dossier, référence interne, une expression régulière reste l'outil approprié. Elle doit être ancrée au début et à la fin de la chaîne, faute de quoi elle acceptera toute valeur contenant un fragment valide, erreur classique et silencieuse. Les expressions doivent rester courtes et commentées : une expression de quarante caractères qu'on ne peut plus relire six mois plus tard est un défaut de conception, pas une preuve de compétence. Un commentaire d'une ligne indiquant le format attendu en langage courant suffit à rendre l'ensemble maintenable.
Les listes de valeurs autorisées
Toute donnée provenant d'une liste déroulante, de boutons radio ou d'un jeu fini de possibilités doit être comparée à une liste explicite. C'est la validation la plus sûre qui soit, puisqu'elle n'accepte que ce qui est prévu au lieu de tenter d'exclure ce qui est dangereux. Cette approche par autorisation, plutôt que par interdiction, devrait être le réflexe par défaut : la liste des valeurs acceptables est connue et courte, celle des valeurs nuisibles est inconnue et infinie. Cette liste doit par ailleurs être définie côté serveur et non reconstruite à partir de ce que le formulaire a envoyé, sans quoi la protection disparaît entièrement.
Les longueurs et les caractères
La longueur doit être mesurée en caractères et non en octets dès que des accents ou des caractères non latins sont possibles, ce qui suppose d'employer les fonctions adaptées aux chaînes multioctets. Une limite de longueur protège la base de données, dont les colonnes ont une taille, et évite les enregistrements tronqués silencieusement. Une limite haute est également une protection élémentaire contre les envois massifs destinés à saturer un traitement. Elle doit rester cohérente avec la limite déclarée dans le formulaire, un utilisateur ne devant jamais découvrir une contrainte au moment du refus.
Les dates et les nombres
Une date se valide en tentant de la construire à partir du format attendu puis en vérifiant qu'aucune erreur n'a été signalée, ce qui écarte les dates impossibles comme le trente et un février. Les nombres décimaux posent la question du séparateur, virgule ou point selon la locale, qui doit être traitée explicitement plutôt que laissée à l'interprétation. Ces deux familles concentrent une part importante des anomalies de saisie constatées en production. Le format de saisie proposé à l'utilisateur doit d'ailleurs être indiqué clairement à côté du champ, ce qui réduit considérablement le nombre de rejets.
Les fichiers téléversés
Un fichier reçu se valide sur sa taille, sur son type réel déterminé à partir de son contenu et non de son extension ni du type déclaré par le navigateur, et sur l'absence d'erreur de transfert. Le nom du fichier doit être entièrement reconstruit plutôt que repris, un nom fourni par un client pouvant contenir des séquences de traversée de répertoire. C'est le champ le plus dangereux d'un formulaire et celui qui mérite le contrôle le plus strict. Le fichier doit en outre être stocké en dehors du répertoire accessible publiquement lorsque son contenu n'a pas vocation à être servi directement.
| Type de donnée | Outil natif | Piège fréquent |
|---|---|---|
| Entier | Filtre avec bornes | Conversion avant vérification |
| Décimal | Filtre dédié | Séparateur décimal non traité |
| Adresse électronique | Filtre dédié | Expression régulière écrite à la main |
| Choix dans une liste | Comparaison à une liste autorisée | Confiance dans le champ envoyé |
| Format métier | Expression régulière ancrée | Absence d'ancrage |
| Date | Construction depuis un format | Dates impossibles acceptées |
| Texte libre | Longueur en caractères | Mesure en octets |
| Fichier | Type réel et taille | Confiance dans l'extension |
Construire une validation lisible
L'organisation compte davantage que le choix des fonctions. Une validation dispersée finit toujours par comporter un trou, et ce trou sera dans le champ auquel personne ne pensait.
Une déclaration par formulaire
Le plus efficace consiste à décrire les règles dans une structure de données placée en un seul endroit, un tableau associant chaque champ à son type, à son caractère obligatoire et à ses contraintes. Le code de validation devient alors une boucle générique parcourant cette déclaration, et l'ajout d'un champ se réduit à une ligne. Cette approche donne en trente lignes ce qu'une bibliothèque offre en plusieurs milliers, avec l'avantage de rester entièrement compréhensible par qui reprend le projet. Elle a aussi le mérite de rendre la liste des champs attendus lisible d'un coup d'œil, ce qui est précieux au moment d'ajouter une fonctionnalité.
Valider tout avant de traiter
Le traitement ne doit commencer qu'une fois l'ensemble des champs contrôlés, et non champ par champ avec un arrêt au premier problème. L'utilisateur doit voir toutes ses erreurs d'un coup plutôt que de les découvrir une par une au fil des soumissions successives. Cette exigence d'ergonomie a une conséquence technique directe sur la structure du code, qui doit accumuler les erreurs dans une collection plutôt que d'interrompre le flux dès la première rencontrée. Sur un formulaire de commande ou d'inscription, cette différence se mesure directement dans le taux d'abandon.
Séparer format et règle métier
Vérifier qu'une date est bien formée et vérifier qu'elle se situe après la date du jour sont deux contrôles de nature différente. Le premier est générique et réutilisable, le second dépend du contexte. Les mélanger produit un code où la logique métier se cache au milieu de contrôles techniques, ce qui la rend invisible en relecture. La séparation en deux étapes successives, format puis règles, rend l'ensemble nettement plus facile à faire évoluer. Elle permet aussi de réutiliser toute la couche de format d'un projet à l'autre, ce qui n'est jamais le cas des règles métier.
Traiter les dépendances entre champs
Certaines règles portent sur plusieurs champs à la fois : une date de fin postérieure à une date de début, un champ obligatoire seulement si une case est cochée, un montant cohérent avec une quantité. Ces contrôles ne peuvent pas s'exprimer dans une déclaration par champ et méritent une étape distincte, exécutée après la validation individuelle. Les oublier est fréquent et produit des enregistrements incohérents que la base accepte parfaitement. Ces contrôles gagnent à être écrits sous forme de fonctions nommées explicitement, leur intention étant rarement évidente à la relecture.
Ne jamais faire confiance aux identifiants
Un identifiant reçu dans un formulaire désigne un enregistrement, et rien ne garantit qu'il appartienne à l'utilisateur qui l'envoie. La validation de format ne dit rien de l'autorisation. Ce contrôle d'appartenance est une étape à part entière, distincte de la validation, et son oubli constitue une des vulnérabilités les plus répandues des applications web, très facile à exploiter en modifiant simplement un nombre dans une requête. La règle est de toujours filtrer la requête de lecture par le propriétaire, plutôt que de lire puis de comparer, ce qui laisse moins de place à l'oubli.
Préparer les requêtes, toujours
Une donnée validée n'est pas pour autant sûre à insérer dans une requête par concaténation. Les requêtes préparées avec paramètres liés sont la seule protection correcte, indépendamment de toute validation préalable. Les deux mécanismes sont complémentaires et non substituables, comme le rappelle notre article consacré à l'injection SQL et à sa prévention.
Défauts constatés en revue de formulaires. Les deux premiers sont exploitables directement, les trois autres relèvent de la fiabilité et de l'ergonomie.
Restituer les erreurs correctement
Une validation techniquement irréprochable dont les messages sont incompréhensibles produit des abandons de formulaire, ce qui coûte plus cher qu'un défaut de sécurité théorique.
Réafficher les valeurs saisies
Un formulaire renvoyé vide après une erreur est la première cause d'abandon. Les valeurs correctes doivent être réaffichées, ce qui suppose de les conserver et de les réinjecter dans les champs, en les échappant selon le contexte. Ce détail d'ergonomie relève de la même mécanique que la validation et doit être traité en même temps, faute de quoi il sera ajouté plus tard et mal, en particulier sur les champs à choix multiples. Sur un formulaire long, cette précaution vaut à elle seule plusieurs points de taux de complétion.
Des messages qui expliquent
Un message indiquant qu'une valeur est invalide n'aide personne. Il doit préciser ce qui est attendu, avec un exemple lorsque le format est particulier. Cette rédaction prend cinq minutes par champ et change complètement le taux de complétion des formulaires longs. Elle relève de la même exigence que le reste de l'interface, et elle est presque toujours confiée par défaut au développeur alors qu'elle gagnerait à être relue par quelqu'un qui connaît les utilisateurs. Un message rédigé à la deuxième personne, indiquant l'action à accomplir plutôt que la nature de l'erreur, donne presque toujours de meilleurs résultats.
Ne rien révéler d'inutile
Les messages ne doivent pas divulguer d'informations sur le fonctionnement interne : nom de table, contrainte de base de données, chemin de fichier, existence d'un compte. Un message indiquant qu'une adresse électronique n'existe pas dans la base permet d'énumérer les comptes. La formulation doit rester générique sur tout ce qui touche à l'authentification, précaution qui ne coûte rien et qui ferme une voie de reconnaissance très employée.
Associer chaque message à son champ
Les messages doivent apparaître à côté du champ concerné et non regroupés en tête de page, où ils obligent l'utilisateur à faire le rapprochement lui même. L'association technique se fait par la clé du champ, ce qui suppose que la collection d'erreurs soit indexée par nom de champ. C'est un choix de structure à faire dès le départ, très pénible à introduire après coup dans un code qui accumule les messages dans une simple liste.
Journaliser les rejets anormaux
Un formulaire rejeté parce qu'un champ caché contient une valeur inattendue ne relève pas d'une erreur d'utilisateur mais d'une tentative de manipulation. Ces rejets méritent une trace dans les journaux, avec l'adresse d'origine, ce qui permet de détecter une exploration automatisée. Distinguer les erreurs de saisie ordinaires des anomalies structurelles rend cette journalisation exploitable, là où tout consigner produit un volume que personne ne relira jamais.
Tester la validation
Les tests doivent porter sur les valeurs limites, les valeurs vides, les champs absents et les valeurs manifestement hostiles. Un jeu d'une vingtaine de cas par formulaire couvre l'essentiel et se rejoue à chaque modification. C'est le type de test le plus rentable qui soit, parce que la validation est du code à branches multiples, précisément celui où les régressions passent inaperçues lorsqu'on ne vérifie que le chemin nominal.
💬 0 commentaires
✍️ Laisser un commentaire
Créez un compte gratuit ou connectez-vous pour commenter.