Chapitre 13.3 — Pont Symfony ↔ WordPress
Partie 13 : Interopérabilité — Chapitre 3/5 Version de référence : Symfony 7.4 / WordPress REST API (
wp/v2).
⏱️ TL;DR
- WordPress expose une REST API (
/wp-json/wp/v2/...) : articles, pages, médias, types personnalisés, taxonomies. On la consomme depuis Symfony avec un client typé (chapitre 13.1).- Lecture publique : sans auth. Écriture / contenu privé : Application Passwords (basic auth), un plugin JWT, ou OAuth.
- Cas d’usage phare : WordPress headless — l’équipe contenu publie dans WP (qu’elle adore), et l’app Symfony (ou le front Next.js) consomme ce contenu via l’API. Le meilleur des deux mondes.
- On met en cache le contenu externe (il change peu) pour la performance et la résilience.
- Lien direct avec votre cours WordPress (headless WP,
wpr-books) : ici, c’est Symfony qui joue le rôle du consommateur.- Pour un dev Next.js : vous savez déjà consommer WP en headless côté Next ; ici, Symfony s’intercale comme backend qui agrège/enrichit ce contenu.
🎯 Objectifs
- Consommer l’API REST de WordPress depuis Symfony.
- Gérer l’authentification (Application Passwords).
- Encapsuler WP dans un client typé et mettre en cache.
- Identifier les cas d’usage (headless, agrégation).
1. L’API REST de WordPress
Toute installation WordPress moderne expose /wp-json/wp/v2/ :
| Endpoint | Contenu |
|---|---|
GET /wp-json/wp/v2/posts | articles (avec pagination, filtres) |
GET /wp-json/wp/v2/pages | pages |
GET /wp-json/wp/v2/media | médias |
GET /wp-json/wp/v2/categories / tags | taxonomies |
GET /wp-json/wp/v2/<cpt> | types de contenu personnalisés (ex. books) |
Chaque réponse est du JSON paginé (en-têtes X-WP-Total, X-WP-TotalPages). C’est exactement le contenu que, dans votre cours WordPress, vous exposiez pour un front headless — ici, le consommateur est Symfony.
2. Un client WordPress typé
On encapsule WP dans un client scopé + typé (chapitre 13.1) :
final class WordPressClient
{
public function __construct(
#[Autowire(service: 'wordpress.client')] // base_uri = %env(WP_URL)%
private readonly HttpClientInterface $client,
) {}
/** @return array<int, array> les derniers articles */
public function latestPosts(int $perPage = 10): array
{
return $this->client->request('GET', '/wp-json/wp/v2/posts', [
'query' => ['per_page' => $perPage, '_embed' => true], // _embed = inclut auteur, média…
])->toArray();
}
public function post(int $id): array
{
return $this->client->request('GET', "/wp-json/wp/v2/posts/$id", [
'query' => ['_embed' => true],
])->toArray();
}
}Le paramètre _embed demande à WP d’inclure les ressources liées (auteur, image à la une, termes) dans la réponse — pratique pour éviter des allers-retours.
3. Authentification pour l’écriture / le privé
La lecture de contenu public ne nécessite pas d’auth. Pour créer/modifier du contenu ou lire du privé, on s’authentifie. La méthode la plus simple et native : les Application Passwords de WordPress (un mot de passe applicatif par intégration, en basic auth) :
# client scopé authentifié
framework:
http_client:
scoped_clients:
wordpress.client:
base_uri: '%env(WP_URL)%'
auth_basic: ['%env(WP_USER)%', '%env(WP_APP_PASSWORD)%']public function createPost(string $title, string $content): array
{
return $this->client->request('POST', '/wp-json/wp/v2/posts', [
'json' => ['title' => $title, 'content' => $content, 'status' => 'draft'],
])->toArray();
}Alternatives : un plugin JWT (jetons), OAuth, ou WPGraphQL (si vous préférez GraphQL, comme vu en Partie 11.7 côté API Platform).
⚠️ Piège : les Application Passwords sont des secrets (accès en écriture à WP !) → coffre de secrets (chapitre 3.4), jamais committés. Et donnez à l’utilisateur WP dédié le minimum de droits nécessaires (principe du moindre privilège) : un compte « intégration » qui ne peut faire que ce dont l’app a besoin.
4. Mettre en cache le contenu externe
Le contenu WP change peu (un article publié reste stable). Appeler WP à chaque requête utilisateur est lent et fragile (si WP est down, votre app l’est aussi). On met en cache :
use Symfony\Contracts\Cache\CacheInterface;
public function latestPosts(int $perPage = 10): array
{
return $this->cache->get("wp_latest_$perPage", function ($item) use ($perPage) {
$item->expiresAfter(300); // 5 min
return $this->fetchLatestPosts($perPage);
});
}Le composant Cache (Partie 15) mémorise la réponse. On gagne en performance (pas d’appel réseau à chaque fois) et en résilience (si WP hoquette, on sert le cache). On peut invalider le cache via un webhook WP (chapitre 13.2) quand un article change.
5. Cas d’usage : l’architecture « WP contenu + Symfony métier »
Un pattern très courant en agence : l’équipe éditoriale utilise WordPress (qu’elle maîtrise) pour le blog / les pages marketing, et l’application Symfony consomme ce contenu (pour l’afficher, l’enrichir, le mixer avec ses données métier), avant de tout exposer au front Next.js. Vous connaissez WP et Symfony et Next.js → vous livrez cette architecture entière.
💡 Pour un dev Next.js : côté front, vous pourriez consommer WP directement. Faire transiter par Symfony ajoute une couche — utile quand on veut agréger WP avec des données métier, normaliser/mettre en cache le contenu, ou cacher WP au public. Le choix dépend du besoin ; savoir faire les deux (Next→WP direct, ou Next→Symfony→WP) est précisément votre valeur.
✏️ Exercices
Exercice 1. Écrivez une méthode latestPosts qui récupère les 5 derniers articles WordPress avec leur auteur et image à la une.
✅ Solution
public function latestPosts(): array
{
return $this->client->request('GET', '/wp-json/wp/v2/posts', [
'query' => ['per_page' => 5, '_embed' => true],
])->toArray();
}_embed inclut l’auteur et l’image à la une (dans _embedded), évitant des requêtes supplémentaires. Idéalement, on met le résultat en cache (5-10 min).
Exercice 2. Quelle méthode d’authentification native de WordPress pour créer des articles depuis Symfony, et quelle précaution ?
✅ Solution
Les Application Passwords (WordPress natif) : un mot de passe applicatif utilisé en basic auth. Précaution : c’est un secret avec accès en écriture → le stocker dans le coffre de secrets (jamais committé), et créer un utilisateur WP dédié avec le minimum de droits (moindre privilège). Alternatives : plugin JWT, OAuth.
Exercice 3. Pourquoi mettre en cache le contenu WordPress consommé par Symfony ?
✅ Solution
Pour la performance (éviter un appel réseau à WP à chaque requête utilisateur) et la résilience (si WP est momentanément indisponible, on sert la version cachée au lieu de tomber en panne). Le contenu WP change peu, donc un cache de quelques minutes est très efficace ; on peut l’invalider via un webhook WP quand un article change.
🧠 Quiz de révision
1. Quel endpoint WordPress liste les articles ?
GET /wp-json/wp/v2/posts (JSON paginé ; _embed pour inclure auteur/média/termes).
2. Comment consomme-t-on WP proprement depuis Symfony ?
Via un client typé + scopé (WordPressClient) qui encapsule l’URL de base, l’auth et les méthodes métier (latestPosts(), post()).
3. Quelle auth native pour écrire dans WP ?
Les Application Passwords (basic auth), stockées comme secrets. Alternatives : plugin JWT, OAuth, WPGraphQL.
4. Pourquoi mettre en cache le contenu WP ?
Performance (moins d’appels réseau) et résilience (servir le cache si WP est down). Le contenu change peu.
5. Quel est l’intérêt de faire transiter WP par Symfony plutôt que Next→WP direct ?
Agréger WP avec des données métier, normaliser/mettre en cache le contenu, cacher WP au public — quand le besoin le justifie. Sinon, Next peut consommer WP directement.
Prochain chapitre : 13.4 — Pont Symfony ↔ Moodle — piloter Moodle par ses Web Services.