Hypermedia-Links statt hartcodierter Client-URLs
HATEOAS macht eine PHP-API zur echten Zustandsmaschine: statt dass Clients URLs selbst zusammenbauen, liefert jede Antwort die Links mit, die im aktuellen Zustand tatsächlich erlaubt sind. Dieser Artikel zeigt eine praktische HATEOAS-Umsetzung in PHP mit HAL-Format, einem eigenen Link-Builder und konkreten Beispielen aus einem Bestellprozess.
Inhaltsverzeichnis
- 1. Was HATEOAS wirklich bedeutet
- 2. Die Grundidee: Ressourcen als Zustandsmaschine
- 3. HAL als praktisches Format für HATEOAS-Antworten
- 4. Einen Link-Builder in PHP selbst bauen
- 5. Conditional Links: Aktionen abhängig vom Zustand
- 6. Discoverability: Navigation statt hartcodierter URLs
- 7. Versionierung und API-Evolution durch Hypermedia
- 8. Performance-Überlegungen bei Link-Generierung
- 9. HATEOAS im Vergleich zu reinem JSON
- 10. Zusammenfassung
- 11. FAQ
1. Was HATEOAS wirklich bedeutet
HATEOAS steht für Hypermedia as the Engine of Application State und ist der am häufigsten missverstandene Teil von Roy Fieldings REST-Dissertation. Die meisten sogenannten REST-APIs erfüllen nur die Basisanforderungen: Ressourcen, HTTP-Verben, Statuscodes. HATEOAS geht einen Schritt weiter und verlangt, dass der Client seinen nächsten Schritt aus der Antwort selbst ableitet, statt Wissen über URL-Strukturen fest im Code zu verankern. Eine Antwort mit HATEOAS enthält also nicht nur Daten, sondern auch die Links, die im aktuellen Zustand tatsächlich gültig sind.
In PHP bedeutet das konkret: jede Ressourcenklasse bekommt neben ihren Daten eine Sammlung von Links, typischerweise unter dem Schlüssel _links. Ein Bestellungsobjekt im Zustand "offen" liefert einen Link zum Stornieren mit, ein Bestellungsobjekt im Zustand "versandt" liefert stattdessen einen Link zur Sendungsverfolgung. Der Client muss die Zustandsübergänge nicht kennen, er liest sie aus der Antwort. Das reduziert die Kopplung zwischen Client und Server erheblich und macht HATEOAS zu einem wirksamen Werkzeug gegen brüchige Frontend-Integrationen.
Wichtig ist die Abgrenzung: HATEOAS ist kein Ersatz für OpenAPI-Dokumentation und auch kein Selbstzweck. Es lohnt sich vor allem dort, wo Ressourcen komplexe Zustandsübergänge haben, etwa Bestellprozesse, Workflow-Engines oder Genehmigungsketten. Für simple CRUD-Endpunkte ohne Zustandslogik ist der Zusatzaufwand oft nicht gerechtfertigt, ein pragmatischer Blick auf den tatsächlichen Nutzen gehört daher an den Anfang jeder HATEOAS-Einführung.
2. Die Grundidee: Ressourcen als Zustandsmaschine
Der Kern von HATEOAS ist die Vorstellung, dass eine Ressource zu jedem Zeitpunkt einen klar definierten Zustand hat und dass aus diesem Zustand eine begrenzte Menge erlaubter Übergänge folgt. Eine Bestellung im Zustand pending erlaubt Stornieren und Bezahlen, im Zustand paid erlaubt sie nur noch Stornieren mit Rückerstattung, im Zustand shipped erlaubt sie ausschließlich das Verfolgen der Sendung. Diese Übergänge in Links abzubilden ist die eigentliche Arbeit bei einer HATEOAS-Implementierung, nicht das bloße Anhängen einer self-URL.
In der Praxis modelliert man das am saubersten, indem man die Zustandslogik dort belässt, wo sie bereits existiert, meist in einer Domain-Klasse oder einem State-Machine-Objekt, und der HATEOAS-Präsentationsschicht nur die Frage stellt: welche Übergänge sind von hier aus erlaubt. Diese Trennung verhindert, dass Zustandslogik in zwei parallelen Implementierungen dupliziert wird, einmal für die Geschäftsregeln und einmal für die Link-Generierung.
<?php
declare(strict_types=1);
/**
* Domain state machine for an order resource.
* HATEOAS links are derived from this, never duplicated.
*/
final class OrderState
{
public function __construct(private readonly string $status)
{
}
/**
* Returns the list of allowed transitions for the current status.
*
* @return string[]
*/
public function allowedTransitions(): array
{
return match ($this->status) {
'pending' => ['cancel', 'pay'],
'paid' => ['cancel', 'ship'],
'shipped' => ['track'],
'cancelled', 'delivered' => [],
default => [],
};
}
public function status(): string
{
return $this->status;
}
}
3. HAL als praktisches Format für HATEOAS-Antworten
HAL, Hypertext Application Language, ist das am weitesten verbreitete konkrete Format für HATEOAS-Antworten in PHP-APIs. Es definiert eine simple Konvention: jede JSON-Ressource bekommt ein _links-Objekt mit benannten Relationen, jede Relation zeigt auf eine href. Eingebettete Ressourcen wandern in ein _embedded-Objekt. Diese Konvention ist bewusst minimal gehalten, dadurch ist HAL leicht in bestehende JSON-APIs zu integrieren, ohne die komplette Antwortstruktur umzubauen.
Der Vorteil von HAL gegenüber einer eigenen Ad-hoc-Link-Struktur liegt in der Vorhersagbarkeit: Client-Bibliotheken für HAL existieren in praktisch jeder Sprache, und Entwickler, die HAL einmal gesehen haben, verstehen sofort, wie eine neue HAL-Antwort zu lesen ist. Für PHP-APIs, die HATEOAS ernsthaft umsetzen wollen, ist HAL daher meist die pragmatischste Wahl gegenüber komplexeren Alternativen wie JSON:API oder Siren, die zwar mächtiger, aber auch deutlich aufwendiger zu implementieren sind.
{
"id": 4821,
"status": "paid",
"total": 129.90,
"_links": {
"self": { "href": "/orders/4821" },
"cancel": { "href": "/orders/4821/cancel", "method": "POST" },
"ship": { "href": "/orders/4821/ship", "method": "POST" }
},
"_embedded": {
"customer": {
"id": 77,
"name": "M. Schmidt",
"_links": { "self": { "href": "/customers/77" } }
}
}
}
4. Einen Link-Builder in PHP selbst bauen
Statt Links an jeder Stelle manuell zu verketten, lohnt sich eine kleine, wiederverwendbare Link-Builder-Klasse. Sie kennt die Basis-URL der API, kann Routen anhand von Namen und Parametern auflösen und liefert konsistent strukturierte HAL-Link-Objekte zurück. In einem Symfony- oder Slim-Projekt bindet man dafür den vorhandenen URL-Generator ein, in einem framework-freien Setup reicht eine simple Routen-Tabelle mit Platzhaltern.
Der entscheidende Vorteil eines zentralen Link-Builders: ändert sich später ein URL-Präfix oder eine Routen-Struktur, muss nur eine Stelle angepasst werden statt Dutzender verstreuter String-Konkatenationen. Gerade bei HATEOAS-Antworten mit vielen Relationen pro Ressource zahlt sich diese Zentralisierung schnell aus, weil inkonsistente Links sonst zu den häufigsten Bugs in Hypermedia-APIs gehören.
<?php
declare(strict_types=1);
/**
* Minimal link builder producing HAL-style link arrays.
*/
final class HalLinkBuilder
{
public function __construct(private readonly string $baseUrl)
{
}
/**
* Builds a single HAL link entry.
*
* @param string $path Relative path, e.g. "/orders/4821/cancel"
* @param string $method HTTP method allowed for this link
* @return array{href: string, method: string}
*/
public function link(string $path, string $method = 'GET'): array
{
return [
'href' => rtrim($this->baseUrl, '/') . $path,
'method' => $method,
];
}
/**
* Builds the full _links block for an order based on its allowed transitions.
*
* @param int $orderId
* @param string[] $transitions
* @return array<string, array{href: string, method: string}>
*/
public function orderLinks(int $orderId, array $transitions): array
{
$links = ['self' => $this->link("/orders/{$orderId}")];
foreach ($transitions as $transition) {
$links[$transition] = $this->link("/orders/{$orderId}/{$transition}", 'POST');
}
return $links;
}
}
5. Conditional Links: Aktionen abhängig vom Zustand
Das eigentliche Herzstück von HATEOAS ist die bedingte Erzeugung von Links. Ein Link darf nur dann in der Antwort erscheinen, wenn die zugehörige Aktion im aktuellen Zustand tatsächlich erlaubt ist. Fehlt der Link, weiß der Client ohne zusätzliche Anfrage, dass die Aktion gerade nicht verfügbar ist, ein einfacher, aber wirkungsvoller Mechanismus, der clientseitige Zustandsprüfungen überflüssig macht.
In der Umsetzung bedeutet das: die HATEOAS-Serialisierung fragt nicht "gibt es eine Route für Stornieren", sondern "ist Stornieren im aktuellen Zustand dieser konkreten Ressource erlaubt". Diese Unterscheidung ist entscheidend, weil sie Autorisierungslogik und Zustandslogik in die Link-Generierung einfließen lässt. Ein Bestellungsobjekt zeigt einem normalen Kunden also andere Links als einem Support-Mitarbeiter, selbst wenn beide dieselbe Bestellung im selben Zustand betrachten.
<?php
declare(strict_types=1);
/**
* Serializes an order into a HAL representation with conditional links.
*/
final class OrderHalSerializer
{
public function __construct(private readonly HalLinkBuilder $links)
{
}
/**
* @param OrderState $order
* @param bool $isSupportAgent Whether extra support-only actions apply
* @return array<string, mixed>
*/
public function serialize(OrderState $order, int $orderId, bool $isSupportAgent): array
{
$transitions = $order->allowedTransitions();
if ($isSupportAgent && $order->status() !== 'cancelled') {
$transitions[] = 'force-refund';
}
return [
'id' => $orderId,
'status' => $order->status(),
'_links' => $this->links->orderLinks($orderId, $transitions),
];
}
}
6. Discoverability: Navigation statt hartcodierter URLs
Ein häufig unterschätzter Nutzen von HATEOAS ist die Entkopplung des Clients von der URL-Struktur der API. Ohne HATEOAS muss ein Frontend wissen, dass eine Stornierung unter POST /orders/{id}/cancel liegt, dieses Wissen ist hartcodiert und bricht bei jeder URL-Umstrukturierung. Mit HATEOAS folgt der Client stattdessen der Relation cancel aus der zuletzt geladenen Antwort, die tatsächliche URL bleibt für den Client irrelevant.
Diese Discoverability zahlt sich besonders in Multi-Client-Umgebungen aus, wenn mehrere Frontend-Teams, mobile Apps und Drittanbieter-Integrationen dieselbe API konsumieren. Ändert sich die interne Routenstruktur, bleibt jeder Client funktionsfähig, solange er weiterhin über Relationsnamen statt über feste Pfade navigiert. Das macht HATEOAS zu einem praktischen Werkzeug für langlebige, öffentlich konsumierte PHP-APIs.
7. Versionierung und API-Evolution durch Hypermedia
HATEOAS reduziert, aber ersetzt keine Versionierungsstrategie. Wo sich reine Datenfelder oder Statuscodes ändern, hilft Hypermedia wenig, dafür braucht es weiterhin klare Vertragsregeln zwischen Client und Server. Wo sich jedoch Workflows ändern, etwa ein neuer Zwischenschritt in einem Genehmigungsprozess, zeigt HATEOAS seine Stärke: der Client muss den neuen Schritt nicht kennen, er folgt einfach dem neu hinzugekommenen Link, sofern er nicht explizit auf einen fest verdrahteten alten Ablauf angewiesen ist.
In der Praxis kombiniert man HATEOAS daher meist mit additiven Änderungen: neue Relationen dürfen jederzeit ergänzt werden, ohne bestehende Clients zu brechen, weil unbekannte Links einfach ignoriert werden. Entfernte oder umbenannte Relationen sind hingegen Breaking Changes und müssen wie jede andere Breaking Change behandelt werden, inklusive Deprecation-Zeitraum und Kommunikation an konsumierende Teams.
8. Performance-Überlegungen bei Link-Generierung
Die Generierung von Links ist selten der Flaschenhals einer PHP-API, kann es aber werden, wenn Listen-Endpunkte Hunderte von Ressourcen zurückgeben und jede Ressource ihre eigenen bedingten Links berechnen muss. In diesem Fall lohnt es sich, die Zustandslogik pro Ressource so effizient wie möglich zu halten und teure Berechnungen, etwa Berechtigungsprüfungen, für eine ganze Liste zu bündeln statt sie pro Element einzeln auszuführen.
Ein weiterer Performance-Aspekt betrifft eingebettete Ressourcen im _embedded-Block: werden Kundendaten bei jeder Bestellung mit eingebettet, entstehen bei Listen-Endpunkten schnell N+1-Abfragen. Hier hilft dasselbe Muster wie bei klassischem Eager Loading: verwandte Ressourcen für die gesamte Liste in einer einzigen Datenbankabfrage vorladen, statt pro Ressource neu zu laden, bevor die HATEOAS-Serialisierung überhaupt startet.
9. HATEOAS im Vergleich zu reinem JSON
Die folgende Tabelle stellt eine HATEOAS-Antwort einer klassischen, rein datengetriebenen JSON-Antwort gegenüber und zeigt, in welchen Punkten sich beide Ansätze in der Praxis unterscheiden.
| Aspekt | Reines JSON | HATEOAS / HAL |
|---|---|---|
| Client-Wissen über URLs | Hartcodiert im Client | Aus Antwort gelesen |
| Erlaubte Aktionen erkennbar | Nur über separate Dokumentation | Direkt in _links sichtbar |
| Implementierungsaufwand | Gering | Moderat bis hoch |
| Robustheit bei URL-Änderungen | Bricht Clients | Clients bleiben funktionsfähig |
| Geeignet für | Einfache CRUD-Endpunkte | Komplexe Workflows, öffentliche APIs |
Der Vergleich zeigt, dass HATEOAS keine universelle Lösung ist, sondern ein gezielt einzusetzendes Werkzeug für PHP-APIs mit echten Zustandsübergängen. Wo diese Komplexität fehlt, überwiegt der Mehraufwand den Nutzen, wo sie vorhanden ist, reduziert HATEOAS die Kopplung zwischen Client und Server messbar.
Mironsoft
PHP-API-Design, Hypermedia und langlebige Backend-Architektur
HATEOAS und Hypermedia sauber in PHP umsetzen?
Wir entwerfen PHP-APIs mit HAL-Links, bedingter Zustandslogik und einer wartbaren Link-Builder-Architektur, damit Clients Zustände navigieren statt URLs zu duplizieren.
API-Architektur-Review
Analyse bestehender Endpunkte auf Zustandslogik und HATEOAS-Potenzial
HAL-Implementierung
Link-Builder, bedingte Relationen und konsistente Serialisierung aufbauen
Client-Integration
Frontend- und Partner-Teams bei der Hypermedia-Navigation begleiten
10. Zusammenfassung
HATEOAS in PHP lohnt sich überall dort, wo Ressourcen echte Zustandsübergänge haben: Bestellprozesse, Genehmigungsketten, Workflow-Engines. Die praktische Umsetzung folgt einem klaren Muster: Zustandslogik bleibt in der Domain, ein Link-Builder erzeugt konsistente HAL-Links, und die Serialisierung fragt bei jeder Relation, ob die zugehörige Aktion im aktuellen Zustand für den konkreten Nutzer erlaubt ist. Diese bedingte Link-Generierung ist der eigentliche Wert von HATEOAS, nicht das bloße Anhängen einer self-URL.
Für einfache CRUD-Endpunkte ohne nennenswerte Zustandslogik lohnt sich der zusätzliche Implementierungsaufwand meist nicht, HATEOAS entfaltet seinen Nutzen erst bei komplexeren, öffentlich konsumierten PHP-APIs mit mehreren Client-Teams. Wer diesen Punkt vor der Einführung klärt, vermeidet unnötigen Aufwand und setzt Hypermedia gezielt dort ein, wo es tatsächlich Kopplung reduziert.
HATEOAS in PHP: Das Wichtigste auf einen Blick
Grundprinzip
Ressourcen liefern die im aktuellen Zustand erlaubten Links direkt mit, statt URL-Wissen im Client zu verankern.
Format
HAL mit _links und _embedded ist die pragmatischste Wahl für PHP-APIs, breit unterstützt und einfach umzusetzen.
Umsetzung
Zentraler Link-Builder plus bedingte Link-Generierung basierend auf Zustand und Berechtigung des anfragenden Nutzers.
Einsatzbereich
Workflows mit echten Zustandsübergängen, nicht für simple CRUD-Endpunkte ohne Zustandslogik.