Skip to Content

Chapitre 13.1 — HttpClient : consommer des API externes

Partie 13 : Interopérabilité — Chapitre 1/5 Version de référence : Symfony 7.4 (composant HttpClient).

⏱️ TL;DR

  • HttpClient est le client HTTP de Symfony : HttpClientInterface::request($method, $url, $options). Options utiles : query, json, headers, auth_bearer, auth_basic.
  • Les réponses sont paresseuses : request() retourne tout de suite ; le corps n’est récupéré qu’à getContent()/toArray() (ce qui permet des requêtes concurrentes).
  • Retries avec RetryableHttpClient ; clients scopés par hôte (base_uri, en-têtes/auth par défaut) ; clients typés injectables.
  • Pour les tests : MockHttpClient simule les réponses (aucun appel réseau).
  • C’est du serveur-à-serveur : Symfony consomme WordPress/Moodle/Stripe/n’importe quelle API.
  • Pour un dev Next.js : HttpClientfetch/axios/undici, mais avec scoping, retries et mock intégrés.

🎯 Objectifs

  • Faire des requêtes HTTP авec options (query, json, auth).
  • Exploiter les réponses paresseuses et la concurrence.
  • Configurer des clients scopés/typés et des retries.
  • Simuler les appels en test.

1. Une requête de base

composer require symfony/http-client
use Symfony\Contracts\HttpClient\HttpClientInterface; final class WeatherClient { public function __construct(private readonly HttpClientInterface $client) {} public function current(string $city): array { $response = $this->client->request('GET', 'https://api.example.com/weather', [ 'query' => ['city' => $city], // ?city=... 'headers' => ['Accept' => 'application/json'], 'auth_bearer' => 'MY_API_TOKEN', // Authorization: Bearer ... ]); return $response->toArray(); // décode le JSON en tableau (déclenche la requête) } }

Options fréquentes : query (query string), json (corps JSON, ajoute le bon Content-Type), body (corps brut/form), headers, auth_bearer, auth_basic: [$user, $pass], timeout.

2. Réponses paresseuses et concurrence

request() ne bloque pas : elle prépare la requête et rend la main. La requête réseau est réellement effectuée quand on lit la réponse (getContent(), toArray(), getStatusCode()). Conséquence : on peut lancer plusieurs requêtes puis lire les résultats — elles s’exécutent en parallèle.

$responses = []; foreach ($cities as $city) { $responses[$city] = $this->client->request('GET', $url, ['query' => ['city' => $city]]); // pas encore exécutées : on empile } foreach ($responses as $city => $response) { $data[$city] = $response->toArray(); // exécution concurrente au fil des lectures }

C’est le moyen simple d’appeler N endpoints en parallèle sans gérer de promesses — utile pour agréger plusieurs sources (chapitre 13.3-13.4).

3. Gérer les erreurs

Par défaut, lire le corps d’une réponse 4xx/5xx lève une exception. On peut vérifier le statut avant :

$response = $this->client->request('GET', $url); if ($response->getStatusCode() !== 200) { // getStatusCode() ne lève pas ; getContent() lèverait sur 4xx/5xx throw new \RuntimeException('API externe indisponible : ' . $response->getStatusCode()); }

Les exceptions du composant (TransportExceptionInterface, ClientExceptionInterface 4xx, ServerExceptionInterface 5xx) permettent un try/catch ciblé.

4. Retries

Les API externes ont des hoquets (5xx, timeouts). On enveloppe le client avec RetryableHttpClient pour réessayer automatiquement (avec backoff) :

# config/packages/framework.yaml framework: http_client: default_options: max_retries: 3 # réessaie les échecs transitoires retry_failed: delay: 1000 multiplier: 2

5. Clients scopés et typés

Appeler la même API à plusieurs endroits en répétant l’URL de base, l’auth et les en-têtes est fastidieux. On configure un client scopé par hôte :

framework: http_client: scoped_clients: moodle.client: base_uri: '%env(MOODLE_URL)%' query: { moodlewsrestformat: 'json' } wordpress.client: base_uri: '%env(WP_URL)%' auth_basic: ['%env(WP_USER)%', '%env(WP_APP_PASSWORD)%']

On injecte le client scopé par son nom (#[Autowire]) ou, mieux, on crée un client typé (une classe qui encapsule l’API) :

final class MoodleClient { public function __construct( #[Autowire(service: 'moodle.client')] // le client scopé private readonly HttpClientInterface $client, ) {} public function coreCourseGetCourses(): array { return $this->client->request('GET', '/webservice/rest/server.php', [ 'query' => ['wsfunction' => 'core_course_get_courses', 'wstoken' => '...'], ])->toArray(); } }

Ce client typé (MoodleClient, WordPressClient) est la brique des chapitres 13.3-13.4 : il encapsule l’API externe derrière des méthodes métier claires, injectables et testables.

6. Mocker en test

En test, on n’appelle pas le vrai service : MockHttpClient renvoie des réponses simulées.

use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; $mock = new MockHttpClient([ new MockResponse(json_encode(['temp' => 21]), ['http_code' => 200]), ]); $client = new WeatherClient($mock); // $client->current('Paris') renvoie ['temp' => 21] sans réseau

C’est indispensable pour des tests rapides et déterministes (Partie 14).

💡 Pour un dev Next.js : HttpClientfetch/undici, mais avec le scoping (comme une instance axios.create({ baseURL })), les retries intégrés, la concurrence paresseuse et le mock natif. Comme pour la conso d’API dans Next.js (Partie 11.8), c’est du serveur-à-serveur : ici Symfony est le client d’autres services (WordPress, Moodle, Stripe…). Encapsulez chaque API tierce dans un client typé — vous ne dispersez jamais des URLs en dur.

✏️ Exercices

Exercice 1. Écrivez une méthode qui GET https://api.example.com/users/{id} avec un Bearer token et renvoie le tableau décodé.

✅ Solution

public function getUser(int $id): array { return $this->client->request('GET', "https://api.example.com/users/$id", [ 'auth_bearer' => $this->token, 'headers' => ['Accept' => 'application/json'], ])->toArray(); }

toArray() décode le JSON (et lève sur 4xx/5xx). Idéalement, token et l’URL de base viennent d’un client scopé.

Exercice 2. Comment appeler 10 endpoints en parallèle avec HttpClient ?

✅ Solution

Grâce aux réponses paresseuses : lancer les 10 request() (elles ne bloquent pas, on les empile dans un tableau), puis itérer pour lire (toArray()/getContent()) — les requêtes s’exécutent concurremment au fil des lectures. Pas besoin de gérer des promesses ; le multiplexage est géré par le composant.

Exercice 3. Pourquoi encapsuler une API externe dans un client typé (ex. MoodleClient) plutôt que d’appeler HttpClient partout ?

✅ Solution

Pour centraliser l’URL de base, l’auth, les en-têtes et la gestion d’erreurs à un endroit, exposer des méthodes métier claires (getCourses()) au lieu d’URLs dispersées, faciliter le test (mock du client typé) et le remplacement. C’est le même principe que les services (Partie 5) : on dépend d’une abstraction de l’API externe, pas de détails HTTP éparpillés.

🧠 Quiz de révision

1. Comment fait-on une requête GET avec query params et Bearer ?

$client->request('GET', $url, ['query' => [...], 'auth_bearer' => $token]), puis ->toArray()/->getContent().

2. Pourquoi les réponses sont-elles paresseuses ?

request() retourne immédiatement ; la requête réseau se déclenche à la lecture. Cela permet d’exécuter plusieurs requêtes en parallèle.

3. Qu’est-ce qu’un client scopé ?

Un client préconfiguré pour un hôte (base_uri, en-têtes/auth par défaut), qu’on injecte par son nom — évite de répéter la config à chaque appel.

4. Comment ajouter des retries automatiques ?

Via RetryableHttpClient / la config http_client.default_options.max_retries (avec backoff) — réessaie les échecs transitoires.

5. Comment tester du code qui appelle une API externe ?

Avec MockHttpClient (+ MockResponse) : des réponses simulées, sans appel réseau (tests rapides et déterministes).


Prochain chapitre : 13.2 — Webhooks : recevoir & émettre — recevoir proprement et notifier des tiers.