PSR Standards im Überblick: PHP FIG Interoperabilität von PSR 3 bis PSR 20
AI generated
<?php
8.4
PHP · PHP FIG · Paket Ökosystem
PSR Standards im Überblick
welcher PHP FIG Standard für welches Problem

PSR Standards sind der Grund, warum ein beliebiger Logger, ein beliebiger Cache Adapter und ein beliebiger HTTP Client in praktisch jedem modernen PHP Projekt austauschbar sind, ohne dass Anwendungscode angepasst werden muss. Wer die wichtigsten PSR Standards von Logging über Caching bis Event Dispatching kennt, wählt Bibliotheken gezielter aus und vermeidet unnötige Kopplung an einzelne Hersteller.

19 Min. Lesezeit PSR-3 · PSR-6 · PSR-7 · PSR-11 · PSR-14 · PSR-18 PHP 8.4 · PHP FIG

1. Was PSR Standards lösen und was nicht

PSR Standards sind Empfehlungen der PHP Framework Interop Group, kurz PHP FIG, einem Zusammenschluss großer PHP Projekte wie Symfony, Laminas und WordPress. Ihr Zweck ist ausschließlich Interoperabilität: ein PSR Standard definiert eine Schnittstelle, meist ein oder mehrere Interfaces, gegen die sich Bibliotheken programmieren, statt gegen eine konkrete Implementierung. Dadurch lässt sich eine Implementierung später austauschen, ohne dass der aufrufende Code angepasst werden muss.

Ein verbreitetes Missverständnis: PSR Standards sind keine Framework Vorgaben. Kein PSR Standard schreibt vor, wie eine Anwendung strukturiert sein muss, welches Framework verwendet wird oder wie die Business Logik aussieht. PSR-3 etwa schreibt nur vor, dass ein Logger die Methode info, warning oder error mit bestimmten Parametern anbietet, nicht wohin die Log Zeilen tatsächlich geschrieben werden. Diese bewusste Beschränkung auf reine Schnittstellen ist der Grund, warum PSR Standards seit über einem Jahrzehnt praktisch unverändert stabil bleiben.

Die PSR Standards, die im Folgenden im Zentrum stehen, PSR-3, PSR-6 und PSR-16, PSR-7 und PSR-17, PSR-11, PSR-14 sowie PSR-18, decken jeweils ein wiederkehrendes Infrastrukturproblem ab: Logging, Caching, HTTP Nachrichten, Dependency Injection Container, Event Kommunikation und HTTP Requests. Wer diese sechs Bereiche kennt, versteht den Großteil dessen, was moderne PHP Bibliotheken heute an Schnittstellen voraussetzen.

2. PSR-3: Logger Interface als gemeinsame Sprache

PSR-3 definiert das LoggerInterface mit acht Methoden, die den RFC 5424 Log Leveln entsprechen: emergency, alert, critical, error, warning, notice, info und debug. Jede dieser Methoden akzeptiert eine Nachricht und ein optionales context Array für strukturierte Zusatzdaten. Bibliotheken, die gegen PSR-3 programmieren, wissen nichts über Monolog, nichts über den Symfony Logger und nichts darüber, ob Log Zeilen in eine Datei, nach Syslog oder in einen zentralen Log Aggregator geschrieben werden.

Der praktische Nutzen dieses PSR Standards zeigt sich beim Austausch der Logging Implementierung. Eine Anwendung, die zunächst Monolog nutzt und später auf eine Cloud native Logging Lösung wechselt, muss lediglich den Container Eintrag für LoggerInterface anpassen, keine einzige Zeile Anwendungscode ändert sich. Das NullLogger Objekt aus PSR-3 selbst ist zudem ein nützliches Default Objekt für Tests und für optionale Logger Parameter, die im Produktivbetrieb nicht zwingend benötigt werden.


<?php

declare(strict_types=1);

namespace Mironsoft\Billing;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

final class InvoiceProcessor
{
    public function __construct(
        private readonly LoggerInterface $logger = new NullLogger(),
    ) {
    }

    public function process(int $invoiceId): void
    {
        $this->logger->info('Processing invoice', ['invoice_id' => $invoiceId]);

        try {
            // ... business logic, independent of the concrete logger
        } catch (\Throwable $e) {
            $this->logger->error('Invoice processing failed', [
                'invoice_id' => $invoiceId,
                'exception' => $e->getMessage(),
            ]);
            throw $e;
        }
    }
}

3. PSR-6 und PSR-16: zwei Cache Standards, ein Zweck

Für Caching existieren zwei parallele PSR Standards: PSR-6 mit CacheItemPoolInterface und CacheItemInterface, sowie PSR-16 als bewusst schlankere Alternative mit SimpleCache Interface. PSR-6 unterstützt Konzepte wie deferred Speicherung und explizites Commit mehrerer Cache Items gleichzeitig, während PSR-16 mit get, set, delete und has direkt auf einfache Anwendungsfälle zielt, ohne den Umweg über ein Cache Item Objekt.

In der Praxis nutzen die meisten Anwendungen PSR-16, weil die API unmittelbarer ist und keine explizite Objekterstellung für jeden Cache Zugriff erfordert. Bibliotheken, die dagegen komplexere Cache Strategien implementieren, etwa mit Tags oder verzögerter Persistierung mehrerer Werte, greifen eher zu PSR-6. Symfony Cache implementiert beide PSR Standards gleichzeitig über dieselbe zugrunde liegende Adapter Architektur, sodass Anwendungen nicht zwischen den beiden Schnittstellen wählen müssen, sondern beide parallel nutzen können.


<?php

declare(strict_types=1);

namespace Mironsoft\Catalog;

use Psr\SimpleCache\CacheInterface;

final class ProductPriceResolver
{
    public function __construct(
        private readonly CacheInterface $cache,
    ) {
    }

    public function getPrice(string $sku): float
    {
        $cacheKey = "product_price_{$sku}";

        // PSR-16: works with any adapter (Redis, Memcached, filesystem, array)
        $cached = $this->cache->get($cacheKey);
        if ($cached !== null) {
            return (float) $cached;
        }

        $price = $this->calculatePrice($sku);
        $this->cache->set($cacheKey, $price, ttl: 3600);

        return $price;
    }

    private function calculatePrice(string $sku): float
    {
        // ... expensive price calculation
        return 19.99;
    }
}

4. PSR-7 und PSR-17: HTTP Messages und Factories

PSR-7 definiert unveränderliche Objekte für Request, Response, Stream, Uri und Uploaded File, das Fundament praktisch jedes modernen PHP HTTP Layers. Der entscheidende Designaspekt: alle PSR-7 Objekte sind immutable. Ein Aufruf wie withHeader gibt eine neue Instanz mit dem geänderten Header zurück, statt das ursprüngliche Objekt zu mutieren. Das verhindert unerwartete Seiteneffekte, wenn dasselbe Request Objekt an mehrere Middleware Schichten weitergereicht wird.

PSR-17 ergänzt PSR-7 um Factory Interfaces, mit denen neue Request, Response und Stream Objekte erzeugt werden, ohne eine konkrete Implementierung wie Guzzle PSR-7 oder Nyholm PSR-7 direkt zu instanziieren. Dieser PSR Standard ist besonders relevant für Bibliotheksautoren: eine HTTP Middleware kann PSR-17 Factories per Constructor Injection entgegennehmen und funktioniert dadurch mit jeder PSR-7 kompatiblen Implementierung, unabhängig davon, welche der Anwendungsentwickler tatsächlich installiert hat.


<?php

declare(strict_types=1);

namespace Mironsoft\Http;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;

final class JsonResponseFactory
{
    public function __construct(
        private readonly ResponseFactoryInterface $responseFactory,
        private readonly StreamFactoryInterface $streamFactory,
    ) {
    }

    // Works with any PSR-7/PSR-17 implementation (Nyholm, Guzzle, Laminas)
    public function create(array $data, int $status = 200): ResponseInterface
    {
        $body = $this->streamFactory->createStream(json_encode($data, JSON_THROW_ON_ERROR));

        return $this->responseFactory
            ->createResponse($status)
            ->withHeader('Content-Type', 'application/json')
            ->withBody($body);
    }
}

5. PSR-11: Container Interface für Dependency Injection

PSR-11 definiert das denkbar schlankeste Interface unter allen PSR Standards: ContainerInterface mit nur zwei Methoden, get und has. Der Zweck ist explizit begrenzt: PSR-11 standardisiert nicht, wie ein Container Objekte konfiguriert oder Abhängigkeiten auflöst, sondern ausschließlich, wie ein bereits konfigurierter Container von außen abgefragt wird. Diese bewusste Beschränkung erlaubt es Frameworks wie Symfony, Laminas oder PHP-DI, ihre jeweils eigene, oft sehr unterschiedliche interne Konfigurationslogik beizubehalten.

In der Praxis nutzen Bibliotheken PSR-11 selten direkt für eigene Objekte, sondern eher, um selbst als Consumer eines fremden Containers zu funktionieren, etwa in Middleware Systemen wie PSR-15, die Handler Klassennamen aus einem Container auflösen müssen. Wichtig: der direkte Zugriff auf einen Container innerhalb von Anwendungscode, das sogenannte Service Locator Muster, gilt allgemein als Anti Pattern und sollte konsequent durch Constructor Injection der tatsächlich benötigten Abhängigkeit ersetzt werden.

6. PSR-14: Event Dispatcher als entkoppelte Kommunikation

PSR-14 standardisiert Event Dispatching über zwei zentrale Interfaces: EventDispatcherInterface mit einer einzigen dispatch Methode, und ListenerProviderInterface, das für ein gegebenes Event Objekt passende Listener zurückliefert. Anders als bei klassischen Observer Implementierungen mit Event Namen als Strings arbeitet PSR-14 typisiert: das Event Objekt selbst bestimmt über seine Klasse, welche Listener aufgerufen werden, was IDE Unterstützung und statische Analyse erheblich erleichtert.

Ein weiteres zentrales Merkmal dieses PSR Standards ist die optionale StoppableEventInterface Schnittstelle. Implementiert ein Event dieses Interface, kann ein Listener die weitere Verarbeitung stoppen, etwa wenn ein früherer Listener bereits eine abschließende Entscheidung getroffen hat. Symfony EventDispatcher implementiert PSR-14 vollständig, sodass in Symfony geschriebene Event Listener auch außerhalb des Frameworks funktionieren, solange die Anwendung PSR-14 kompatible Event Objekte verwendet.


<?php

declare(strict_types=1);

namespace Mironsoft\Orders;

use Psr\EventDispatcher\StoppableEventInterface;

final class OrderPlacedEvent implements StoppableEventInterface
{
    private bool $stopped = false;

    public function __construct(
        public readonly int $orderId,
        public readonly float $totalAmount,
    ) {
    }

    public function stopPropagation(): void
    {
        $this->stopped = true;
    }

    public function isPropagationStopped(): bool
    {
        return $this->stopped;
    }
}

// Dispatching against the PSR-14 interface, not a concrete implementation
$event = $dispatcher->dispatch(new OrderPlacedEvent(orderId: 4821, totalAmount: 129.90));

7. PSR-18: HTTP Client ohne Guzzle Abhängigkeit im Code

PSR-18 definiert ClientInterface mit einer einzigen Methode sendRequest, die ein PSR-7 RequestInterface entgegennimmt und ein PSR-7 ResponseInterface zurückgibt. Vor PSR-18 band jede Bibliothek, die HTTP Requests versenden musste, entweder eine konkrete Guzzle Version fest ein oder implementierte eine eigene, inkompatible Abstraktion. Beides führte regelmäßig zu Versionskonflikten, wenn zwei Abhängigkeiten unterschiedliche Guzzle Major Versionen voraussetzten.

Mit PSR-18 als PSR Standard deklariert eine Bibliothek lediglich eine Abhängigkeit von psr/http-client, ohne festzulegen, welche konkrete Implementierung, Guzzle, Symfony HttpClient oder curl basierte Alternativen wie php-http/curl-client, tatsächlich zum Einsatz kommt. Das Paket php-http/discovery ergänzt PSR-18 praktisch, indem es zur Laufzeit automatisch eine installierte, kompatible Implementierung findet, falls die Anwendung keine explizit im Container konfiguriert hat.


<?php

declare(strict_types=1);

namespace Mironsoft\Payment;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;

final class PaymentGatewayClient
{
    public function __construct(
        private readonly ClientInterface $httpClient,
        private readonly RequestFactoryInterface $requestFactory,
    ) {
    }

    // No dependency on Guzzle, Symfony HttpClient or any concrete implementation
    public function charge(string $token, float $amount): bool
    {
        $request = $this->requestFactory
            ->createRequest('POST', 'https://gateway.example.com/charges')
            ->withHeader('Content-Type', 'application/json');

        $response = $this->httpClient->sendRequest($request);

        return $response->getStatusCode() === 200;
    }
}

8. Abgrenzung zu PSR-1, PSR-4 und PSR-12

Nicht jeder PSR Standard definiert eine Laufzeit Schnittstelle. PSR-1 und PSR-12 regeln reinen Code Stil, etwa Einrückung, Klammersetzung und Namenskonventionen, ohne jede Auswirkung auf das Laufzeitverhalten. PSR-4 wiederum standardisiert ausschließlich das Autoloading, also die Zuordnung von Namespaces zu Verzeichnisstrukturen, ebenfalls ohne Interface oder Laufzeitverhalten.

Diese Unterscheidung ist wichtig, weil Entwickler häufig alle PSR Standards in einen Topf werfen. Die in diesem Artikel behandelten Standards, PSR-3, PSR-6 und PSR-16, PSR-7 und PSR-17, PSR-11, PSR-14 sowie PSR-18, definieren tatsächliche Interfaces, gegen die Anwendungscode zur Laufzeit programmiert. PSR-1, PSR-4 und PSR-12 betreffen dagegen ausschließlich statische Struktur und Formatierung, nicht das Verhalten zur Laufzeit, und werden entsprechend von komplett anderen Tools durchgesetzt, etwa PHP CS Fixer für PSR-12 und Composer selbst für PSR-4.

9. PSR Standards im direkten Vergleich

Die folgende Tabelle ordnet die wichtigsten PSR Standards nach Anwendungsbereich, zentralem Interface und typischem Einsatzort in einer PHP Anwendung.

PSR Standard Anwendungsbereich Zentrales Interface Typischer Einsatzort
PSR-3 Logging LoggerInterface Anwendungs und Fehler Protokollierung
PSR-6 / PSR-16 Caching CacheInterface Redis, Memcached, Dateisystem Cache
PSR-7 / PSR-17 HTTP Nachrichten RequestInterface Middleware, Router, HTTP Layer
PSR-11 Dependency Injection ContainerInterface Framework Container Abfrage
PSR-14 Event Kommunikation EventDispatcherInterface Entkoppelte Domain Events
PSR-18 HTTP Client ClientInterface API Aufrufe an externe Dienste

Auffällig ist, dass alle sechs in dieser Tabelle gelisteten PSR Standards jeweils nur ein einziges oder wenige, sehr fokussierte Interfaces definieren. Diese absichtliche Schlankheit ist kein Zufall, sondern das zentrale Designprinzip der PHP FIG: je kleiner ein Interface, desto leichter lässt es sich implementieren, desto seltener ändert es sich, und desto stabiler bleibt die Interoperabilität über Jahre hinweg.

Mironsoft

PHP Architektur, Interoperabilität und Composer Tooling

Code, der gegen PSR Standards statt gegen konkrete Bibliotheken programmiert ist?

Wir prüfen eure bestehende Architektur auf unnötige Kopplung an einzelne Hersteller und ersetzen sie durch PSR-3, PSR-6/16, PSR-7/17, PSR-11, PSR-14 und PSR-18 kompatible Schnittstellen.

Architektur Review

Analyse, wo direkte Abhängigkeiten statt PSR Interfaces genutzt werden

Refactoring

Austausch fest verdrahteter Implementierungen gegen PSR konforme Interfaces

Schulung

Praxisworkshop zu PSR Standards und deren Einsatz im Teamalltag

10. Zusammenfassung

Die wichtigsten PSR Standards lösen jeweils ein spezifisches Interoperabilitätsproblem: PSR-3 standardisiert Logging über LoggerInterface, PSR-6 und PSR-16 standardisieren Caching mit unterschiedlicher API Tiefe, PSR-7 und PSR-17 standardisieren unveränderliche HTTP Nachrichten samt Factories, PSR-11 standardisiert die reine Abfrage eines Dependency Injection Containers, PSR-14 standardisiert typisierte Event Kommunikation, und PSR-18 standardisiert den Versand von HTTP Requests ohne feste Bindung an Guzzle oder eine andere konkrete Implementierung.

Der gemeinsame Nenner aller sechs Standards ist bewusste Schlankheit: jedes Interface deckt genau einen Anwendungsfall ab, ohne Framework spezifische Annahmen. Wer eigene Bibliotheken gegen diese PSR Standards statt gegen konkrete Implementierungen programmiert, macht Code über Jahre hinweg austauschbar, testbar und unabhängig von der Wahl eines bestimmten Frameworks oder Herstellers.

PSR Standards im Überblick — Das Wichtigste auf einen Blick

Logging und Caching

PSR-3 LoggerInterface für Protokollierung, PSR-6/PSR-16 für austauschbare Cache Implementierungen ohne Vendor Lock-in.

HTTP Layer

PSR-7/PSR-17 für unveränderliche HTTP Nachrichten und Factories, PSR-18 für Requests ohne feste Guzzle Bindung.

Container und Events

PSR-11 für reine Container Abfrage, PSR-14 für typisierte, entkoppelte Event Kommunikation zwischen Komponenten.

Abgrenzung

PSR-1, PSR-4 und PSR-12 regeln Code Stil und Autoloading, keine Laufzeit Interfaces, klar getrennt von den hier behandelten Standards.

11. FAQ: PSR Standards im Überblick

1Was sind PSR Standards genau?
Interoperabilitäts-Empfehlungen der PHP FIG, meist als Interfaces, gegen die Bibliotheken statt gegen konkrete Implementierungen programmieren.
2Schreiben PSR Standards eine Framework Struktur vor?
Nein, sie definieren nur Schnittstellen für Infrastrukturprobleme, keine Vorgaben zu Architektur oder Business Logik.
3Was regelt PSR-3?
Das LoggerInterface mit acht Methoden nach RFC 5424 Log Level, unabhängig vom tatsächlichen Ziel der Log Zeilen.
4Unterschied zwischen PSR-6 und PSR-16?
PSR-6 nutzt Cache Item Objekte mit deferred Speicherung, PSR-16 bietet eine direktere API mit get, set und delete.
5Warum sind PSR-7 Objekte immutable?
Um Seiteneffekte in Middleware Ketten zu vermeiden, withHeader und ähnliche Methoden geben immer eine neue Instanz zurück.
6Wofür wird PSR-11 verwendet?
Für die reine Abfrage eines bereits konfigurierten Containers über get und has, nicht für dessen Konfiguration.
7Vorteil von PSR-14 gegenüber Observer Mustern?
Typisierung über die Klasse des Event Objekts statt String Namen, erleichtert IDE Unterstützung und statische Analyse.
8Warum PSR-18 statt direkt Guzzle?
Entkoppelt von einer konkreten HTTP Client Version, vermeidet Versionskonflikte bei unterschiedlichen Guzzle Anforderungen.
9Zählen PSR-4 und PSR-12 auch dazu?
Ja, aber sie regeln Autoloading und Code Stil, keine Laufzeit Interfaces, und werden von anderen Tools durchgesetzt.
10Müssen alle PSR Standards gleichzeitig genutzt werden?
Nein, jeder Standard löst ein eigenständiges Problem und kann unabhängig eingesetzt werden, je nach tatsächlichem Bedarf.