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.
Inhaltsverzeichnis
- 1. Was PSR Standards lösen und was nicht
- 2. PSR-3: Logger Interface als gemeinsame Sprache
- 3. PSR-6 und PSR-16: zwei Cache Standards, ein Zweck
- 4. PSR-7 und PSR-17: HTTP Messages und Factories
- 5. PSR-11: Container Interface für Dependency Injection
- 6. PSR-14: Event Dispatcher als entkoppelte Kommunikation
- 7. PSR-18: HTTP Client ohne Guzzle Abhängigkeit im Code
- 8. Abgrenzung zu PSR-1, PSR-4 und PSR-12
- 9. PSR Standards im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.