Autre pièce charnière dans le développement d’un bon système back-end WordPress : la création d’une API sur-mesure pour permettre l’interaction avec d’autres services (front-end, outils tiers, sites connexes, etc.)
Heureusement, WordPress propose d’une part, par défaut, de nombreuses routes afin de lire et écrire des données dans un site. Mais surtout, il offre d’autre part aux développeurs un moyen simple et efficace d’enregistrer de nouvelles routes pour répondre à des besoins précis.
Améliorer ses compétences WordPress
Cet article fait partie d’une série de leçons présentant les différentes APIs et fonctions PHP disponibles dans WordPress. Découvrez l’article de présentation Comment devenir un bon développeur back-end WordPress ? pour en savoir plus et accéder aux autres leçons.
Qu’est-ce que l’API REST dans WordPress ?
Définition : une API est une interface de programmation d’application, et le mot REST signifie « REpresentational State Transfer » (transmission d’un état de représentation). C’est un ensemble de concepts permettant de modéliser et d’accéder aux données d’une application web via des objets et des collections interdépendants au format JSON.
L’API REST de WordPress fournit des points de communication REST (des adresses URLs) représentant les publications, les taxonomies et d’autres types de données présents dans le site WordPress.
Un service externe peut alors profiter de l’API pour envoyer et recevoir des données JSON à ces points de terminaison pour interroger, modifier et créer du contenu dans l’application WordPress. Lorsque vous demandez ou envoyez du contenu à l’API, la réponse sera également renvoyée au format JSON.
Voici quelques exemples concrets de routes d’API REST développées pour des cas clients WordPress récents :
- recevoir les webhooks envoyés par SparkPost lors d’événements liés à l’envoi d’e-mails,
- rechercher et retourner une liste de villes, départements et régions pour un champ de recherche auto-complété en AJAX,
- intercepter les interactions utilisateur de l’application mobile freebulle pour lire ou écrire dans un système WordPress,
- enregistrer les détails de réservations d’un outil de planification de wagons pour le Train des Cévennes,
- valider et enregistrer les formulaires de création de produits par les vendeurs de la place de marché MadeByTeachers…
Bref, l’API REST de WordPress est suffisamment flexible pour répondre à tous les besoins d’une application web ou mobile.
Vous avez besoin d’une API pour votre application web ?
Je suis expert dans le développement de back-end WordPress sur-mesure.
Développer de nouvelles routes d’API REST
Code du plugin sur GitHub
Une extension d’exemple accompagne ce tutoriel et vous pouvez la télécharger sur GitHub et l’installez.
Dans le code du plugin que nous allons analyser pour cette leçon, 2 routes d’API maison ont été crées :
- l’une accepte les requêtes POST sur
<site.fr>/wp-json/custom-api-route/create
pour publier un article privé, en envoyant en body un JSON contenant untitle
et uncontent
.- En cas de réponse positive (création), cette route répond avec l’ID du post créé et un token sécurisé assigné à ce post, utilisable par l’autre route pour supprimer ce post.
- l’autre accepte les requêtes DELETE sur
<site.fr>/wp-json/custom-api-route/delete/<id>
afin de supprimer un post précédemment créé. Elle attend un headerX-WP-TOKEN
contenant la valeur du token sécurisé du post correspondant afin de valider la requête.- En cas de réponse positive (suppression), cette route répond avec l’objet
WP_Post
fraîchement supprimé.
- En cas de réponse positive (suppression), cette route répond avec l’objet
Prêts à écrire vos propres endpoints d’API REST ? Alors c’est parti !
Enregistrer une nouvelle route d’API
Si vous êtes un tant soit peu familier avec l’extension des logiques WordPress, alors vous remarquerez que la création d’une nouvelle URL d’API REST est relativement simple.
Il suffit d’appeler la fonction register_rest_route
sur le hook rest_api_init
et de fournir une fonction de rappel (callback) qui aura pour mission de traiter les requêtes sur le nouvel endpoint enregistré (voire les valider si vous ne souhaitez pas utiliser l’option validate_callback
).
function register_routes() {
register_rest_route(
'custom-api-route',
'/delete/(?P<post_id>\d+)',
[
'methods' => 'DELETE',
'callback' => __NAMESPACE__ . '\\process_deletion',
'permission_callback' => function( \WP_REST_Request $request ) {
if ( get_post_meta( (int) $request->get_param( 'post_id' ), 'token', true ) !== $request->get_header( 'X-WP-TOKEN' ) ) {
return new \WP_Error( 'post_deletion_denied', 'You cannot delete this post.' );
}
return true;
},
'args' => [
'post_id' => [
'validate_callback' => function ( $value, \WP_REST_Request $request, $key ) {
if ( get_post_type( (int) $value ) !== 'post' ) {
return new \WP_Error( 'post_id_invalid_value', 'Post not found.' );
}
return true;
},
]
],
]
);
}
add_action( 'rest_api_init', __NAMESPACE__ . '\\register_routes' );
Regardons les paramètres de cette fonction d’un peu plus près :
- le premier est le namespace, c’est à dire le préfixe global qui précèdera vos routes placé après
<site.com>/wp-json/
, - le deuxième est une chaîne de caractère indiquant la route à enregistrer. Elle est soit statique ou peut représenter un regex pour définir des arguments passés via l’URL, comme dans notre cas où un paramètre
$post_id
sera extrait de l’URL en enregistrant la route avec/delete/(?P<post_id>\d+)
, - le troisième est un tableau d’options qui va nous permettre de définir la configuration de notre nouvelle route, avec entre autre :
- la méthode HTTP autorisée, via le paramètre
$options['method']
, - la fonction de rappel à exécuter pour envoyer la réponse, via le paramètre
$options['callback']
, - la fonction validant l’accès à cette route via le paramètre
$options['permission_callback']
, - les spécificités des paramètres fournis par l’URL de la route via le paramètre
$options['args']
.
- la méthode HTTP autorisée, via le paramètre
Seuls les trois premières options sont obligatoires.
Indiquer les paramètres autorisés, les valider et les nettoyer
Reprenons le paramètre $options['args']
(facultatif) utilisé pour enregistrer notre route de suppression de post :
'args' => [
'post_id' => [
'validate_callback' => function ( $value, \WP_REST_Request $request, $key ) {
if ( ! is_numeric( $value ) ) {
return new \WP_Error( 'post_id_invalid_format', 'Post ID should only contain digits.' );
}
if ( get_post_type( (int) $value ) !== 'post' ) {
return new \WP_Error( 'post_id_invalid_value', 'Post not found.' );
}
return true;
},
'sanitize_callback' => function( $value ) {
return absint( $value );
},
]
],
Il va nous permettre d’indiquer à WordPress les différentes logiques de validation que l’on attend sur tel ou tel argument de requête, ainsi que les éventuels nettoyages automatiques que l’on souhaite appliquer sur les valeurs entrantes.
Ce paramètre est en fait un tableau et chaque élément de ce tableau possède, en clé, l’argument à valider/nettoyer (post_id
dans notre exemple) et en valeur un autre tableau où l’on peut fournir :
- un élément en clé
validate_callback
qui sera notre fonction de validation, - un élément en clé
sanitize_callback
qui sera une fonction de nettoyage automatique.
Validation d’un paramètre entrant avec validate_callback
Dans l’exemple ci-dessus, on va donc profiter de ces 2 paramètres pour valider l’ID du post fourni en entrée (via le paramètre post_id
passé dans l’URL) pour :
- s’assurer que c’est bien une valeur numérique,
- et vérifier que le post lié à cet ID est bien du type désiré.
Si une vérification n’est pas valide, on renvoie une erreur via un objet WP_Error()
en indiquant un code d’erreur, un message lisible voire un tableau de données en 3è paramètre pour fournir une réponse encore plus précise aux consommateurs de l’API et modifier le statut HTTP de la réponse.
Un mot sur la modification du statut HTTP des réponses d’API en cas de réussite ou d’erreur
Si vous souhaitez renvoyer une erreur en réponse de la requête avec WP_Error
, sachez que vous pouvez indiquer en 3è paramètre un tableau avec une clé status
et une valeur représentant le statut (ou code) HTTP à renvoyer :
return new \WP_Error(
'invalid_title',
'Please provide a title with a length between 10 and 50 chars.',
[ 'status' => 400 ]
);
Si la validation est bonne et que vous souhaitez par contre envoyer un statut HTTP autre que 200, la réponse à renvoyer doit être un objet WP_REST_Response
instantié avec la valeur du statut en 2è paramètre :
return new \WP_REST_Response(
[ 'success' => true ],
201
);
Nettoyage automatique des valeurs reçues avec sanitize_callback
'args' => [
'content' => [
'sanitize_callback' => function( $value ) {
return wp_kses_post( $value );
},
],
'title' => [
'sanitize_callback' => 'sanitize_text_field',
],
],
Quant au nettoyage d’un paramètre, vous aurez remarqué que c’est assez simple : les fonctions que l’on définit vont recevoir la valeur en entrée, et vont pouvoir appliquer librement des fonctions pour nettoyer cette valeur et s’assurer qu’elle sera sécurisée lors du traitement final de la requête.
Les callbacks définis ici peuvent être :
- des fonctions anonymes (cf le paramètre
content
ci-dessus), - ou simplement le nom d’une fonction de nettoyage qui recevra la valeur du paramètre transmis dans la requête (cf le paramètre
title
ci-dessus).
Référez-vous à ce guide du Theme Handbook pour découvrir toutes les fonctions de nettoyage disponibles dans WordPress.
Ajouter des règles d’autorisation
Il est obligatoire d’indiquer aussi une fonction de rappel pour définir les règles autorisant ou empêchant l’usage de notre route d’API. On utilise pour celà l’option permission_callback
qui est une fonction recevant l’objet WP_REST_Request
en paramètre à travers lequel on peut accéder au détail de la requête (body, headers, cookies, etc.).
Vous pouvez par exemple vérifier si l’utilisateur est connecté et possède un rôle spécifique. Si cette fonction anonyme renvoie un objet WP_Error
, l’appel est court-circuité, une erreur est renvoyée en réponse et la logique de la route n’est pas exécutée. Si par contre la fonction renvoie true
, la logique continue son chemin.
'permission_callback' => function( \WP_REST_Request $request ) {
if ( get_post_meta( (int) $request->get_param( 'post_id' ), 'token', true ) !== $request->get_header( 'X-WP-TOKEN' ) ) {
return new \WP_Error( 'post_deletion_denied', 'You cannot delete this post.', [ 'status' => 403 ] );
}
return true;
},
Dans notre cas, pour la route de suppression d’un post, on autorise la suppression uniquement si l’utilisateur fournit un header X-WP-TOKEN
dont la valeur est équivalente au token de sécurité enregistré en métadonnée du post à supprimer.
Si ces 2 valeurs ne correspondent pas, la demande n’est pas valide et on retourne au consommateur de l’API un message d’erreur avec un statut 403 (denied).
Si elles correspondent, la demande de suppression est considérée comme authentique et valide : la suite de la logique sera exécutée.
Maintenant que l’on a correctement validé la requête et ses paramètres, voyons comment exécuter des logiques.
Exécuter des actions
Le fonction de callback définie dans register_rest_route()
est le cœur de la logique d’une route d’API développée sur-mesure. C’est dans cette fonction qu’on va exécuter les actions propres à une route.
function process_deletion( \WP_REST_Request $request ) {
// Suppression du post.
$deleted_post = wp_delete_post( $request->get_param( 'post_id' ), true );
return [
'success' => ( $deleted_post instanceof \WP_Post ),
'post' => $deleted_post,
];
}
Elle reçoit en paramètre un objet WP_REST_Request
qui contient tous les détails de la requête en question. Dans l’exemple ci-dessus, on va supprimer le post et renvoyer en réponse un tableau associatif (qui deviendra un objet côté JSON). Donner libre cours à votre imagination et exécuter ce que bon vous semble dans cette fonction !
Un point sur la validation tardive
Notez que dans cette fonction de rappel, vous pouvez très bien effectuer encore d’autres logiques de vérification et retourner des WP_Error
si vos règles de validation ne sont pas validées.
C’est une affaire de goût : vous pouvez décider de ne pas écrire beaucoup de paramètres dans register_rest_route()
et centraliser la validation et le nettoyage dans votre fonction de callback, ou faire au contraire comme présenté ici.
J’ai tendance à préfèrer cette seconde technique car elle est plus lisible et maintenable : les fonctions d’enregistrement de mes routes intègrent toutes les « barrières » à passer en amont et les fonctions de rappel ne se concentrent que sur les actions à effectuer, en sachant que les validations sont OK et que les valeurs ont été préalablement nettoyées.
Accéder aux données de la requête
Cet objet $request
possède plein de méthodes pratiques pour accéder aux données que le consommateur de notre API nous envoie. Entre autre :
$request->get_params()
pour récupérer un tableau contenant tous les paramètres (format clés => valeurs) envoyés,$request->get_headers()
pour récupérer un tableau contenant tous les headers envoyés,$request->get_file_params()
pour récupérer les données de fichiersmultipart
envoyés dans le body,- et bien plus encore : vous trouverez une liste complète des méthodes d’accès aux données entrantes
get_*()
ici
Renvoyer une réponse appropriée aux consommateurs
Enfin, il est primordial de construire une réponse adéquate à renvoyer à l’utilisateur de l’API. Nous avons déjà vu comment renvoyer des erreurs proprement avec des WP_Error
que WordPress transformera pour renvoyer un JSON au client.
En cas de réussite, on a deux possibilités. Soit on return
un tableau associatif que l’on compose librement, qui sera servi en JSON à l’utilisateur.
Soit on return
une instantiation de la classe WP_REST_Response
construite :
- avec ce même tableau associatif en premier paramètre servant de corps de réponse,
- en deuxième paramètre, un integer pour définir le statut HTTP de la réponse,
- et en troisième paramètre, un tableau associatif permettant d’ajouter des headers spécifiques dans notre réponse.
return new \WP_REST_Response(
[ 'success' => true ],
201,
[ 'WordPress-Custom-Header' => 1234 ]
);
Et voila ! Après tout ça, notre route d’API maison est maintenant prête à recevoir des appels, valider ou invalider leurs données, les nettoyer, exécuter des actions et renvoyer une réponse adaptée.
Pas mal, non ?
Aller plus loin avec l’API Rest WordPress
Je me permets un petit aparté en bonus de ce tutoriel concernant l’intégration d’un système d’authentification utilisateur en AJAX par JWT dans WordPress. Quelques extensions existent pour ne pas réinventer la roue, comme JWT Auth – WordPress JSON Web Token Authentication.
C’est une telle extension qui m’a permis de gérer l’identification utilisateur en AJAX dans des projets d’applications mobiles comme freebulle ou TechniGrain.
Voici son fonctionnement pour créer une identification d’utilisateur WordPress en AJAX :
– on lance une requête POST sur la route /wp-json/jwt-auth/v1/token
en indiquant le nom d’utilisateur et le mot de passe entrés par l’utilisateur dans un formulaire (dans l’appli mobile par exemple, ou un front React JS),
– si l’authentification est valide, cette route d’API renvoie un statut 200 accompagné d’un long token qu’il faudra stocker côté client,
– tous les futurs appels à des routes d’API nécessitant l’identification de l’utilisateur devront alors contenir un header Authorization: Bearer <token>
: l’extension va intercepter ces appels, analyser ce header, le décoder et automatiquement « identifier » l’utilisateur durant la requête AJAX,
– le traitement de vos routes d’API pourra alors utiliser des fonctions comme get_current_user()
ou get_current_user_id()
comme si un utilisateur était identifié dans son navigateur grâce à un cookie.
Magique non ?
Les services tiers permettent souvent l’enregistrement de webhooks : plutôt que d’interroger leur API pour récupérer une donnée ou effectuer une action, c’est le service qui va appeler une route d’API de notre système WordPress afin de nous communiquer des informations à des moments précis.
Pour réussir la création d’un webhook sur-mesure dans WordPress, il suffit de suivre ce tutoriel et enregistrer votre propre route d’API REST à rendre disponible uniquement pour le service concerné, en pensant bien à intégrer une sécurité (token dans un header, clé d’API dans le body, IP whitelistée, etc.) pour n’accepter que les demandes émises par le service en question.
C’est ce que j’ai développé pour l’extension WP Mail Pro où une route d’API interne au site WordPress est déclarée par le plugin afin de permettre à SparkPost de lui communiquer des événements relatifs à la transmission d’e-mails (réception, ouverture, bounce, etc.).
Plusieurs possibilités :
– d’abord, installer l’extension JSON Formatter dans votre navigateur pour améliorer l’affichage des réponses JSON sur les pages web. Vous pouvez déjà tester des méthodes GET
ainsi, comme par exemple lister les routes accessibles sur votre site sur l’URL <site.com>/wp-json/
– installer Postman ou Insomnia (mon préféré car plus léger) pour effectuer tout type de requêtes (GET, POST, DELETE, etc.) sur des URLs et ainsi tester les interactions avec une API spécifique. Ils vous permettent d’une part de sauvegarder et organiser vos requêtes, de définir les réglages de chacune (headers, body, méthode, etc.), de générer du code (cURL, fetch JS, etc) et bien plus encore. Un must-have pour tout bon développeur back-end souhaitant simplifier ses intégrations avec des APIs externes !
Si vous avez des questions spécifiques au développement d’API dans WordPress, ou souhaitez partager des commentaires, n’hésitez pas à utiliser le formulaire ci-dessous !