'validate_callback' => 'is_numeric' — cette seule ligne, oubliée dans la déclaration d’une route REST personnalisée, a suffi à provoquer une erreur 500 en production sur un projet qui acceptait un paramètre quantite censé toujours être un nombre. Sans validation, une chaîne de caractères passée par erreur atteignait directement le callback principal de la route, qui tentait une opération arithmétique dessus et provoquait une erreur fatale PHP.
Ce type d’incident révèle une confusion fréquente entre deux arguments du schéma d’argument d’une route REST : sanitize_callback et validate_callback. Ils se ressemblent, s’utilisent souvent ensemble, mais remplissent deux rôles bien distincts que WordPress exécute dans un ordre précis.
Ce que fait chacun des deux arguments
validate_callback répond à une question binaire : la valeur reçue est-elle acceptable ? Il doit retourner un booléen (ou un objet WP_Error pour une erreur détaillée). S’il retourne false, WordPress rejette la requête avant même d’exécuter le callback principal de la route, avec une erreur HTTP 400 propre.
sanitize_callback, lui, ne juge pas : il transforme. Sa mission consiste à nettoyer ou convertir la valeur reçue dans le format attendu par le callback principal, par exemple convertir une chaîne "12" en entier 12, ou retirer des balises HTML indésirables d’un champ texte.
L’ordre d’exécution, souvent mal compris
WordPress exécute d’abord validate_callback, puis sanitize_callback, uniquement si la validation a réussi. Cet ordre a une conséquence directe : validate_callback reçoit toujours la valeur brute, non nettoyée, telle qu’envoyée par le client. Écrire une validation qui suppose une valeur déjà nettoyée constitue une erreur fréquente.
register_rest_route( 'boutique/v1', '/stock', array(
'methods' => 'GET',
'callback' => 'boutique_recuperer_stock',
'args' => array(
'quantite' => array(
'required' => true,
'validate_callback' => function( $valeur, $request, $param ) {
if ( ! is_numeric( $valeur ) ) {
return new WP_Error(
'parametre_invalide',
sprintf( 'Le paramètre %s doit être numérique.', $param ),
array( 'status' => 400 )
);
}
return true;
},
'sanitize_callback' => function( $valeur ) {
return absint( $valeur );
},
),
),
) );

Le scénario qui provoque un 500
Sans validate_callback, une requête GET /wp-json/boutique/v1/stock?quantite=abc laisse passer la chaîne "abc" jusqu’au sanitize_callback. Si celui-ci applique absint(), la chaîne non numérique est silencieusement convertie en 0, ce qui masque le problème sans le signaler clairement, et peut fausser une logique métier qui ne s’attend jamais à recevoir zéro pour ce champ. Pire, si le callback principal de la route effectue une opération plus fragile qu’un simple absint() — un accès à un tableau indexé par cette valeur, par exemple — l’absence de validation peut se traduire par une erreur fatale PHP non interceptée, remontée au client sous la forme d’une réponse HTTP 500 peu explicite.
Ajouter un validate_callback transforme cette même requête invalide en une réponse 400 propre, avec un message d’erreur exploitable par le front, avant même que le code métier de la route ne soit atteint.
Les variantes à connaître
- Pour un paramètre simple,
rest_validate_request_arg()etrest_sanitize_request_arg()couvrent la majorité des cas sans callback personnalisé, en s’appuyant sur letypedéclaré dans le schéma (integer,string,boolean). - Le champ
typedu schéma, à lui seul, déclenche déjà une validation basique : un typeintegerrefuse une chaîne non numérique sans callback supplémentaire. - Combiner un
enumavecvalidate_callbackpermet de restreindre un paramètre à une liste fermée de valeurs acceptées, en plus du type.
Variante avec type déclaré, sans callback personnalisé
'args' => array(
'statut' => array(
'type' => 'string',
'enum' => array( 'disponible', 'rupture', 'precommande' ),
),
),
Cette déclaration suffit, sans écrire de fonction, à rejeter automatiquement toute valeur en dehors des trois statuts autorisés.
En résumé
Confondre validate_callback et sanitize_callback revient à sauter l’étape qui empêche une donnée invalide d’atteindre le code métier d’une route REST personnalisée. Le premier décide si la requête continue ; le second adapte la valeur reçue à ce dont le code a réellement besoin. Omettre le premier ne provoque pas toujours une erreur visible immédiatement, ce qui rend ce type de bug particulièrement difficile à repérer avant qu’il ne se manifeste en production sous la forme d’une erreur 500 inattendue.