# Arguments et schéma des routes REST : validate_callback et sanitize_callback

> Déclarer proprement les arguments d'une route REST évite la moitié des bugs de validation. Tour d'horizon du schéma JSON et de ses callbacks.

- Auteur : WordPress Développement
- Publié le : 2020-04-15
- Mis à jour le : 2026-09-30
- Catégorie : Headless &amp; API
- URL : https://www.wpmoderne.fr/headless/arguments-schema-routes-rest-validate-sanitize-callback/

## L’essentiel

- Chaque argument déclare son type, sa validation et son nettoyage
- validate_callback rejette, sanitize_callback transforme
- Erreurs 400 automatiques et cohérentes

La première route REST personnalisée que j'ai écrite validait ses arguments « à la main », avec une série de `if` au début du callback. Ça fonctionnait, mais chaque erreur retournait un message différent, parfois un code HTTP incohérent, et le code devenait vite illisible dès que la route acceptait plus de deux paramètres. Le tableau `args` de `register_rest_route()` existe justement pour éviter ça : il externalise la validation et le nettoyage, avant même que le callback principal ne soit appelé.

Cet article détaille comment déclarer des arguments avec un schéma JSON, la différence entre `validate_callback` et `sanitize_callback`, et les pièges que j'ai rencontrés en les combinant sur des routes réelles.

## Déclarer un argument avec son schéma

Chaque entrée du tableau `args` décrit un paramètre attendu par la route, sous forme d'un sous-ensemble de JSON Schema : `type`, `required`, `default`, `enum`, `minimum`/`maximum` pour les nombres, et les deux callbacks qui nous intéressent ici.

> L'essentiel à retenir : Chaque argument déclare son type, sa validation et son nettoyage ; validate_callback rejette, sanitize_callback transforme ; Erreurs 400 automatiques et cohérentes

Voici une route complète qui liste des événements d'un agenda. Elle déclare quatre arguments de natures différentes, ce qui permet de voir chaque mécanisme à l'œuvre :

```
add_action( 'rest_api_init', 'agenda_declarer_routes' );

function agenda_declarer_routes() {
	register_rest_route( 'agenda/v1', '/evenements', array(
		'methods'             => WP_REST_Server::READABLE,
		'callback'            => 'agenda_lister_evenements',
		'permission_callback' => '__return_true',
		'args'                => array(
			'par_page'  => array(
				'description'       => "Nombre d'événements par page.",
				'type'              => 'integer',
				'default'           => 10,
				'minimum'           => 1,
				'maximum'           => 50,
				'validate_callback' => 'rest_validate_request_arg',
				'sanitize_callback' => 'rest_sanitize_request_arg',
			),
			'statut'    => array(
				'type'    => 'string',
				'default' => 'a_venir',
				'enum'    => array( 'a_venir', 'passe', 'tous' ),
			),
			'depuis'    => array(
				'type'              => 'string',
				'validate_callback' => 'agenda_valider_date',
			),
			'recherche' => array(
				'type'              => 'string',
				'sanitize_callback' => 'agenda_nettoyer_texte',
			),
		),
	) );
}

function agenda_valider_date( $valeur, $requete, $cle ) {
	if ( ! is_string( $valeur ) ) {
		return false;
	}
	$date = DateTime::createFromFormat( '!Y-m-d', $valeur );
	if ( false === $date || $date->format( 'Y-m-d' ) !== $valeur ) {
		return new WP_Error(
			'agenda_date_invalide',
			sprintf( '%s doit être une date au format AAAA-MM-JJ.', $cle )
		);
	}
	return true;
}

function agenda_nettoyer_texte( $valeur ) {
	return sanitize_text_field( $valeur );
}
```

Les quatre arguments illustrent quatre manières de procéder. `par_page` nomme explicitement les deux fonctions fournies par le cœur, `rest_validate_request_arg()` et `rest_sanitize_request_arg()`, qui lisent le schéma (`type`, `minimum`, `maximum`) dans la déclaration de la route. `statut` ne déclare aucun callback : comme l'argument possède un `type`, WordPress applique de lui-même `rest_parse_request_arg()`, qui valide puis nettoie d'après le schéma. `depuis` remplace la validation par une fonction maison, et `recherche` remplace le nettoyage.

## validate_callback et sanitize_callback : deux rôles distincts

La distinction est simple, mais elle se brouille à l'usage. Le `validate_callback` répond à la question « cette valeur est-elle acceptable ? » : il ne modifie rien, il accepte ou il refuse. Le `sanitize_callback` répond à la question « sous quelle forme voulez-vous la manipuler ? » : il retourne la valeur transformée (entier, chaîne débarrassée de ses balises, tableau normalisé) et n'a pas à juger.

L'ordre d'exécution, dans le cœur, est le suivant : contrôle des paramètres obligatoires (`required`), puis tous les `validate_callback`, puis tous les `sanitize_callback`, puis le `permission_callback`, et enfin le callback principal. Celui-ci ne reçoit donc que des valeurs déjà contrôlées et nettoyées, à condition que la déclaration soit complète.

Le contrat d'un `validate_callback` mérite d'être cité précisément : seuls `false` et un objet `WP_Error` sont considérés comme un refus. Retourner `false` produit le message générique « Invalid parameter. » (traduit selon la langue du site) ; retourner un `WP_Error` permet de fournir un message lisible, comme dans `agenda_valider_date()` ci-dessus. Les trois arguments transmis au callback sont la valeur, l'objet `WP_REST_Request` et le nom du paramètre.

## Le format de l'erreur renvoyée au client

Lorsqu'au moins un argument est refusé, le callback principal n'est jamais appelé. Le client reçoit un code HTTP 400 et un corps JSON dont la clé `data.params` détaille chaque paramètre fautif. Pour un appel à `/wp-json/agenda/v1/evenements?depuis=demain`, la réponse ressemble à ceci :

```
{
  "code": "rest_invalid_param",
  "message": "Invalid parameter(s): depuis",
  "data": {
    "status": 400,
    "params": {
      "depuis": "depuis doit être une date au format AAAA-MM-JJ."
    }
  }
}
```

Cette cohérence est le principal bénéfice pour le front : un seul format d'erreur à traiter, quel que soit l'argument fautif, avec le nom du champ à surligner dans un formulaire. Le paramètre obligatoire manquant produit, lui, le code `rest_missing_callback_param`, également en 400.

## Les pièges rencontrés en combinant les deux

- **Un `sanitize_callback` maison remplace la validation par défaut.** Si vous déclarez `'type' => 'integer'`, `'minimum' => 1` et `'sanitize_callback' => 'absint'` sans `validate_callback`, la valeur `abc` devient silencieusement `0` et la borne minimale n'est jamais contrôlée. Ajoutez `'validate_callback' => 'rest_validate_request_arg'` dès que vous personnalisez le nettoyage.
- **Un callback qui ne retourne rien valide tout.** Une fonction de validation qui oublie son `return` retourne `null`, et `null` n'est pas `false` : la valeur passe. Terminez toujours par un `return true` explicite.
- **Une valeur de type inattendu atteint votre callback.** Dès que vous remplacez la validation du cœur, un paramètre envoyé sous forme de tableau (`?depuis[]=1`) arrive tel quel : d'où le contrôle `is_string()` en tête de `agenda_valider_date()`.
- **Les fonctions natives de PHP reçoivent trois arguments.** WordPress appelle le callback avec la valeur, la requête et le nom du paramètre. Une fonction interne de PHP à un seul paramètre, comme `is_numeric`, peut produire un avertissement ; enveloppez-la dans une fonction à vous.
- **La validation ne remplace pas le `permission_callback`.** Une valeur bien formée n'est pas une valeur autorisée : les deux contrôles répondent à des questions différentes.

## Cas concret : un agenda consommé par un front JavaScript

Sur la route ci-dessus, un appel avec `par_page=-5` ou `par_page=1000` transmettrait la valeur telle quelle à `WP_Query` si la route n'avait aucun schéma, avec des résultats difficiles à prévoir. Avec le schéma, ces appels sont refusés avant d'atteindre la requête, et la documentation de la route (visible en envoyant une requête `OPTIONS` sur son adresse) décrit chaque argument avec son type, ses bornes et sa valeur par défaut. Le développeur du front n'a plus à lire le code PHP pour savoir ce que la route accepte.

> Un argument sans schéma est une promesse non tenue : la route prétend accepter n'importe quoi, puis laisse le hasard décider de la suite.

## Conclusion

Déclarer ses arguments dans `args` déplace la validation là où elle appartient : hors du callback, à un endroit unique, avec des erreurs 400 uniformes. Retenez la règle de répartition (`validate_callback` refuse, `sanitize_callback` transforme), le contrat exact de retour (`false` ou `WP_Error`) et le piège du nettoyage personnalisé qui écarte la validation du schéma. Avec ces trois points, la plupart des bugs de paramètres disparaissent avant d'atteindre votre logique métier.
