Wenn du einen Experten für Symfony oder PHP suchst, bist du hier richtig. Kontaktiere uns


Lass die Nutzer des SDK ihren eigenen HTTP-Client nutzen

· Mathieu Santostefano · 5 Minuten zum Lesen
3 dog heads

Befreie dich von starren Abhängigkeiten in deinen PHP-SDKs. Erfahre, wie du die Standards PSR-7, PSR-17 und PSR-18 zusammen mit PHP-HTTP/Discovery nutzt, um deinen Benutzern die Verwendung ihres bevorzugten HTTP-Clients zu ermöglichen – sei es Guzzle, Symfony HttpClient oder ein anderes Tool. Ein Muss für PHP- und Symfony-Entwickler.

Dieser Artikel behandelt praktische Techniken für SDK-Entwickler, die es Benutzern ermöglichen, ihren eigenen HTTP-Client zu verwenden, statt ihnen die Nutzung eines Clients aufzuzwingen, den sie nicht gewählt haben. Er richtet sich auch an Benutzer, die ihren bevorzugten HTTP-Client innerhalb eines SDKs verwenden möchten, das diesen bereits unterstützt.

Du kennst wahrscheinlich den Dschungel der HTTP-Clients, die im PHP-Ökosystem existieren. Guzzle, Symfony HttpClient, Buzz, CakePHP HTTP usw. Als SDK-Maintainer ist es ein Albtraum, Support für all diese Pakete bereitzustellen. Glücklicherweise gibt es Abstraktionsschichten und Techniken, die uns dabei helfen, die große Mehrheit davon zu berücksichtigen. Lass uns gemeinsam herausfinden, wie das funktioniert!

Genesis

Vor ein paar Monaten besuchte ich den AFUP Day 2025, eine eintägige französische Konferenz. Insbesondere besuchte ich einen Vortrag meines Freundes Nicolas Grekas über HTTP-Clients in PHP. Der Vortrag war nicht ganz so, wie ich ihn mir vorgestellt hatte. Er versetzte das Publikum in die Lage eines SDK-Entwicklers, der vor der Herausforderung steht, einen HTTP-Client in seine Quellcode-Abhängigkeiten zu integrieren.

  • Welchen HTTP-Client?

  • Was, wenn ein SDK-Benutzer bereits einen HTTP-Client in seinen Abhängigkeiten installiert hat?

  • Wie kann ein SDK-Benutzer seinen eigenen HTTP-Client anstelle des von mir bereitgestellten verwenden?

Die meisten dieser Fragen hat Nicolas auf elegante Weise beantwortet. Lass uns in dieses faszinierende Thema eintauchen!

Hier ist eine Aufzeichnung seines Vortrags von der API Platform Con 2024:

API Platform Conference 2024 - Nicolas Grekas - Consuming HTTP APIs in PHP the Right Way!

Statte dich mit den richtigen Werkzeugen aus

Zunächst möchte ich dir einige PSRs (PHP Standard Recommendations) sowie einige nicht so „magische” PHP-Pakete vorstellen, die uns bei unserer Suche helfen können.

PSR

Zunächst sind hier einige PSRs. Falls du damit nicht vertraut bist, betrachte sie als beliebte technische Empfehlungen für Bibliotheksentwickler und deinen eigenen Anwendungscode. Diese PSRs decken viele Themen ab, z. B. Codierungsstil, Autoloading, Cache, Container, Clock und das, woran wir heute interessiert sind: HTTP.

PSR-7: HTTP Message

Diese beschreibt Schnittstellen für Request und Response (sowie einige „Untertypen“ wie Stream, UploadedFile usw.) und wird von PSR-18 als Argumenttyp und Rückgabetyp für die Methode sendRequest verwendet.

PSR-18: HTTP Client

Diese PSR kann wie folgt zusammengefasst werden: Es wird eine gemeinsame Schnittstelle zum Senden von PSR-7-Anfragen und Zurückgeben von PSR-7-Antworten bereitgestellt.

Sie stellt eine Client-Schnittstelle und drei Exception-Schnittstellen bereit, um verschiedene Arten von Fehlern darzustellen.

PSR-17: HTTP Factories

Diese PSR stellt Factories für alle HTTP-Nachrichtenschnittstellen bereit, die in PSR-7 definiert sind. Wie diese Factories genutzt werden können, sehen wir weiter unten.

PHP-Pakete

PSR/HTTP-Client

Dieses Paket enthält den Code im Zusammenhang mit PSR-18 (ClientInterface und drei Exception-Schnittstellen). Genau das brauchen wir, um unseren Code zu typisieren und uns nicht auf eine konkrete Implementierung zu verlassen.

php-http/discovery

Es handelt sich dabei sowohl um eine Codebibliothek als auch um ein Composer-Plugin (seit Version 1.17). Es ist für die automatische Erkennung und Installation von Paketen zuständig, die konkrete Implementierungen von PSR-17- und PSR-18-Schnittstellen bereitstellen.

Es funktioniert zusammen mit den nächsten beiden Paketen.

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

Diese beiden Pakete sind etwas Besonderes, da es sich um virtuelle Pakete handelt. Sie existieren nur im Packagist-Register und werden von „echten” Paketen verwendet, um anzuzeigen, dass sie konforme Implementierungen für PSR-17- und/oder PSR-18-Schnittstellen bereitstellen.

Ein kurzer Hinweis zum Konzept der virtuellen Pakete: Stelle es dir wie PHP-Interfaces vor, aber auf Paketebene. In diesem Beispiel zeigt das virtuelle Paket psr/http-client-implementation in einer SDK-composer.json an, dass das SDK jedes Paket benötigt, das eine konkrete Implementierung von PSR-18 bereitstellt.

Da „psr/http-client” direkt von „psr/http-message” abhängt, müssen wir nicht davon abhängig sein, um auf Nachrichtenschnittstellen wie RequestInterface, ResponseInterface usw. zu verweisen.

Eine letzte nützliche Eigenschaft ist die provide-Eigenschaft des Composer-Schemas. Hier ist eine konkrete Verwendung dieser Eigenschaft:

  • Maintainer des Pakets symfony/http-client können diese Eigenschaft in die „composer.json”-Datei aufnehmen, um anderen Entwicklern mitzuteilen, dass das Paket eine konkrete PSR-18-Implementierung bereitstellt.

"provide": {
    "psr/http-client-implementation": "1.0",
},
  • Als SDK-Entwickler kannst du das virtuelle Paket psr/http-client-implementation zusammen mit php-http/discovery anfordern. Letzteres sorgt als Plugin dafür, dass das SDK eine PSR-18-Implementierung benötigt. Es wird dann in der composer.json der Anwendung, in der es installiert ist, nach einer suchen, indem es die provide-Eigenschaft jeder Abhängigkeit liest.

  • Ein Bild sagt bekanntlich mehr als tausend Worte. Hier ist eines:

Scheme showing how HTTP client works with PSRsDank PHP-HTTP/HTTP-Discovery, das von Foo/SDK benötigt wird, können die Entwickler beider Anwendungen lediglich ihren bevorzugten HTTP-Client und Foo/SDK anfordern. Die Erkennungsfunktion sucht nach jedem Paket, das von der composer.json der Anwendung benötigt wird und psr/http-client-implementation bereitstellt.

Um die Erklärung zu vereinfachen, lasse ich das virtuelle Paket psr/http-factory-implementation hier bewusst weg, das Prinzip ist jedoch dasselbe.

Bereite deinen Code vor

SDK-Codebeispiel

<?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);
    }
}

Hier ist ein grundlegendes Beispiel einer Foo-Klasse, die von einem SDK bereitgestellt wird und in ihrem eigenen Code PSR-18- sowie PSR-17-Schnittstellen verwendet. Dadurch haben SDK-Benutzer die Freiheit, ihren eigenen HTTP-Client mitzubringen.

  • automatisch, dank Psr18ClientDiscovery::find()

  • oder manuell durch Übergabe eines $client-Arguments an den Konstruktor.

Beachte, dass Psr17Factory intern Psr17FactoryDiscovery-Methoden aufruft, um vorhandene konkrete Implementierungen benötigter Factories zu finden, falls null an den Konstruktor übergeben wird.

Code-Beispiel des Benutzers

<?php

namespace App;

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

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

        // your logic
    }

    public function callBarWithMyOwnHttpClient(array $data): void
    {
        // 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);

        // your logic
    }

    public function callBarWithMyOwnHttpClientAndFactories(array $data): void
    {
        // 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);

        // your logic
    }
}

Es folgt ein einfaches Beispiel für den Code eines SDK-Benutzers. Dabei lässt das SDK den passenden HTTP-Client selbst finden (Methode callBar) oder es wird manuell ein Client übergeben (Methode callBarWithMyOwnHttpClient). Es ist sogar möglich, den Client und die Factories zu übergeben (Methode callBarWithMyOwnHttpClientAndFactories). Bevor du diese Objekte übergibst, kannst du sie selbstverständlich für deine Bedürfnisse konfigurieren (Basis-URI, Header usw.).

Solange das SDK auf Schnittstellen basiert, steht es dem SDK-Benutzer völlig frei, eigene Schnittstellenimplementierungen zu erstellen und diese an das SDK-API-Objekt zu übergeben.

Weiterführende Informationen

Selbstverständlich haben wir hier nur an der Oberfläche des Themas gekratzt. Während der Entwicklung eines SDKs können viele weitere Fragen aufkommen, insbesondere, wenn du dich zu einem bestimmten Zeitpunkt auf eine spezifische Funktion eines HTTP-Clients verlassen musst, die nicht von PSR-18 abgedeckt wird.

Du könntest dich beispielsweise fragen, was passiert, wenn ein SDK-Benutzer keinen kompatiblen HTTP-Client in seinen Anwendungsabhängigkeiten hat. Solltest du standardmäßig einen bereitstellen? Welchen? Wie kann man ihn bereitstellen? Wie testet man als SDK-Maintainer seinen Code?

Wenn dir dieser Artikel gefallen hat und du tiefer in das Thema eintauchen möchtest, lass es uns wissen! Vielleicht folgt ein zweiter Teil, um dieses erste Kapitel zu ergänzen!

Ressourcen

Du möchtest diese Strategie in dein eigenes SDK implementieren, weißt aber nicht, wie du anfangen sollst?

Der Entwickler von Symfony, SensioLabs, bietet spezialisierte Beratung und Schulungen an. Damit unterstützt er Ihr Team dabei, Best Practices für die moderne PHP-Entwicklung und HTTP-Client-Integration zu erlernen.

Image