Vous recherchez un expert Symfony et PHP ? Contactez-nous


Venez avec votre propre client HTTP

· Mathieu Santostefano · Temps de lecture: 5 minutes
3 dog heads

Libérez-vous des dépendances rigides de vos SDK PHP. Dans cet article, apprenez à utiliser les normes PSR-7, PSR-17 et PSR-18, ainsi que la bibliothèque php-http/discovery, pour permettre à vos utilisateurs d'utiliser le client HTTP de leur choix, qu'il s'agisse de Guzzle, de Symfony HttpClient ou d'un autre. Un incontournable pour les développeurs PHP et Symfony.

Cet article présente des techniques pratiques permettant aux développeurs de SDK d'autoriser l'utilisation d'un client HTTP choisi par l'utilisateur, plutôt que de le contraindre à utiliser un client imposé. Il s'adresse également aux utilisateurs qui souhaitent utiliser leur client HTTP préféré dans un SDK qui supporte déjà cette flexibilité.

Vous connaissez sans doute la multitude de clients HTTP existant dans l'écosystème PHP : Guzzle, Symfony HttpClient, Buzz, CakePHP HTTP, etc. Pour les mainteneurs de SDK, offrir un support pour tous ces packages est un vrai cauchemar. Heureusement, il existe des couches d'abstraction et des techniques qui nous aident à prendre en charge la grande majorité d'entre eux. Voyons comment y parvenir !

Introduction

Il y a quelques mois, j'ai assisté à l'AFUP Day 2025 à Poitiers et plus particulièrement à une conférence de mon ami Nicolas Grekas sur la bonne façon de consommer des API HTTP en PHP. La conférence n'était pas exactement ce à quoi je m'attendais. Elle a mis le public dans la peau d'un développeur de SDK confronté au dilemme de fournir un client HTTP dans les dépendances de son code source.

  • Quel client HTTP choisir ?

  • Que se passe-t-il si l'utilisateur a déjà un client HTTP installé dans ses dépendances ?

  • Comment un utilisateur peut-il utiliser son propre client HTTP au lieu de celui que je fournis ?

La plupart de ces questions ont été élégamment résolues par Nicolas. Penchons-nous sur ce sujet passionnant !

Voici un enregistrement de cette conférence lors de l'API Platform Con 2024 : API Platform Conference 2024 - Nicolas Grekas - Consuming HTTP APIs in PHP the Right Way!

Équipez-vous des bons outils

Tout d'abord, je vais vous présenter quelques PSR (PHP Standard Recommendations) et des packages PHP (pas si magiques) qui pourront nous aider dans notre quête.

Les PSR

Voici tout d'abord quelques PSR. Si vous n'êtes pas familier avec le concept, considérez-les comme des recommandations techniques populaires à la fois pour les développeurs de packages et pour le code de votre propre application. Ces PSR couvrent de nombreux sujets, tels que le style de codage, l'autoloading, le cache, les conteneurs d'injection de dépendance et, le sujet qui nous intéresse aujourd'hui, HTTP.

PSR-7: HTTP Message

Cette PSR décrit les interfaces pour Request et Response (ainsi que certains « sous-types » tels que Stream, UploadedFile, etc.). Elle est utilisée par la PSR-18 comme type d'argument et de retour pour la méthode sendRequest.

PSR-18: HTTP Client

Cette PSR peut être résumé comme suit :

Une interface commune pour l'envoi de requêtes PSR-7 et le retour de réponses PSR-7.

Elle fournit une ClientInterface et 3 interfaces d'exception pour représenter différents types d'échecs.

PSR-17: HTTP Factories

Cette PSR fournit des factories pour toutes les interfaces de messages HTTP décrites dans le PSR-7. Nous verrons ci-dessous comment tirer parti de ces factories.

Les packages PHP

psr/http-client

Ce package contient le code lié à PSR-18 (ClientInterface et trois interfaces d'exception). C'est exactement ce dont nous avons besoin pour typer notre code et éviter de dépendre d'une implémentation concrète.

php-http/discovery

Il s'agit à la fois d'un package de code et d'un plugin Composer (depuis la version 1.17). Il est responsable de la découverte et de l'installation automatiques de packages fournissant des implémentations concrètes des interfaces issues de PSR-17 et PSR-18. Il fonctionne en collaboration avec les deux packages suivants :

psr/http-client-implementation & psr/http-factory-implementation

Ces deux packages sont un peu spéciaux, car ce sont des packages virtuels. Ils n'existent que dans le registre Packagist et sont utilisés par des packages « réels » pour indiquer qu'ils fournissent des implémentations conformes aux interfaces de PSR-17 et/ou PSR-18.

Une note rapide sur le concept de packages virtuels : considérez-les comme des interfaces PHP, mais au niveau du package. Dans un fichier composer.json de SDK, le package virtuel psr/http-client-implementation en tant que dépendance indique que le SDK a besoin de n'importe quel autre package fournissant une implémentation concrète de PSR-18.

Comme psr/http-client dépend directement de psr/http-message, nous n'avons pas besoin de dépendre de ce dernier pour faire référence à des interfaces de message telles que RequestInterface, ResponseInterface, etc.

Une dernière chose utile à savoir est la propriété provide du schéma JSON de Composer. Voici une utilisation concrète de cette propriété :

  • Pour les mainteneurs de symfony/http-client, cette propriété peut être ajoutée dans le fichier composer.json pour indiquer aux autres développeurs que le package symfony/http-client fournit une implémentation concrète de PSR-18.

"provide": {
    "psr/http-client-implementation": "1.0",
},
  • En tant que développeur de SDK, vous pouvez requérir ce package virtuel psr/http-client-implementation ainsi que le plugin php-http/discovery qui permettra de comprendre que votre SDK a besoin d'une implémentation PSR-18. Composer en cherchera alors une (en lisant la propriété provide de chaque dépendance) dans le composer.json de l'application dans laquelle il est installé.

  • Une image vaut mille mots, en voici une :

Scheme showing how HTTP client works with PSRsGrâce à php-http/http-discovery requis par foo/sdk, les développeurs des deux applications peuvent simplement requérir leur client HTTP préféré et foo/sdk. La fonctionnalité de découverte cherchera tout package requis par le composer.json de l'application qui fournit psr/http-client-implementation.

Pour simplifier l'explication ici, j'ai délibérément omis le package virtuel psr/http-factory-implementation, mais le principe est le même.

Préparez votre code

Exemple de code de SDK

<?php

use Http\Discovery\Psr17Factory;
use Http\Discovery\Psr18ClientDiscovery;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;

namespace Foo\SDK;

final readonly class Api
{
    private const string BASE_URI = 'https://example.com';

    public function __construct(
        private ?ClientInterface $client = null,
        private ?RequestFactoryInterface $requestFactory = null,
        private ?StreamFactoryInterface $streamFactory = null,
    ) {
        $this->client = $client ?: Psr18ClientDiscovery::find();

        $this->factory = new Psr17Factory(
            requestFactory: $requestFactory,
            streamFactory: $streamFactory,
        );
    }

    public function callApi(array $data): ResponseInterface
    {
        $body = $this->factory->createStream(json_encode($data));

        $request = $this->factory->createRequest('POST', self::BASE_URI . '/api/bar')
            ->withHeader('Content-Type', 'application/json')
            ->withBody($body)
        ;

        return $this->client->sendRequest($request);
    }
}

Voici un exemple basique d'une classe Foo fournie par un SDK, tirant parti des interfaces de PSR-18 et PSR-17 dans son propre code. Cela donne la liberté aux utilisateurs du SDK d'utiliser leur propre client HTTP :

  • automatiquement, grâce à Psr18ClientDiscovery::find()

  • manuellement en passant un argument $client au constructeur

Notez ici que la Psr17Factory appelle en interne les méthodes Psr17FactoryDiscovery pour trouver les implémentations concrètes de Factories existantes si null est passé au constructeur.

Exemple de code utilisateur

<?php

namespace App;

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Psr18Client;

final readonly class FooRelatedService
{
    public function callBar(array $data)
    {
        // Inside Foo/SDK/Api, Psr18ClientDiscovery will seek for a concrete implementation of PSR-18
        $fooApi = new Foo\SDK\Api();
        $response = $foo->callApi($data);
    }

    public function callBarWithMyOwnHttpClient(array $data)
    {
        // Standalone instanciation, but dependency injection could be used here.
        $myOwnHttpClient = new Psr18Client(HttpClient::create());

        $fooApi = new Foo\SDK\Api(client: $myOwnHttpClient);
        $response = $foo->callApi($data);
    }

    public function callBarWithMyOwnHttpClientAndFactories(array $data)
    {
        // Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface and others
        $myOwnHttpClientAndFactories = new Psr18Client(HttpClient::create());

        $fooApi = new Foo\SDK\Api(
            client: $myOwnHttpClientAndFactories,
            requestFactory: $myOwnHttpClientAndFactories
            streamFactory: $myOwnHttpClientAndFactories
        );
        $response = $foo->callApi($data);
    }
}

Voici un exemple de code écrit par un utilisateur de SDK, où il laisse le SDK trouver le client HTTP approprié (méthode callBar) ou bien il passe manuellement son propre client HTTP (méthode callBarWithMyOwnHttpClient), voire même en passant un client et des Factories (méthode callBarWithMyOwnHttpClientAndFactories). Bien sûr, avant de passer ces objets, vous pouvez les configurer selon vos besoins (Base URI, Headers, etc.).

Tant que le SDK s'appuie sur des interfaces, l'utilisateur du SDK est totalement libre de créer ses propres implémentations d'interface et de les passer à l'objet Api du SDK.

Pour aller plus loin

Bien sûr, nous n'avons fait qu'effleurer le sujet ici. De nombreuses autres questions peuvent se poser lors du développement d'un SDK, notamment lorsque vous avez besoin de vous appuyer sur une fonctionnalité spécifique d'un client HTTP non couverte par PSR-18.

Vous pouvez également vous demander ce qui se passe si l'utilisateur du SDK n'a pas de client HTTP compatible dans les dépendances de son application. Faut-il en fournir un par défaut ? Lequel ? Comment le fournir ? Comment tester votre code en tant que mainteneur de SDK ?

Si vous avez aimé cet article et souhaitez approfondir le sujet, faites-le-nous savoir ! Une deuxième partie pourrait compléter ce premier chapitre !

Ressources

Vous souhaitez mettre en œuvre cette stratégie dans votre propre SDK, mais vous ne savez pas par où commencer ?

SensioLabs, le créateur de Symfony, vous conseille et organise des formations spécialisées pour aider votre équipe à maîtriser les meilleures pratiques de développement PHP moderne et d'intégration de clients HTTP.

Image