HATEOAS in PHP praktisch umgesetzt: Hypermedia-Links in API-Antworten
AI generated
<?php
8.4
PHP · API Design · REST · Hypermedia
HATEOAS in PHP praktisch umgesetzt
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.

18 Min. Lesezeit HATEOAS · HAL · Hypermedia · API Design PHP 8.4

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" } }
    }
  }
}

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

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.

11. FAQ: HATEOAS in PHP praktisch umgesetzt

1Was bedeutet HATEOAS konkret?
Hypermedia as the Engine of Application State: die Antwort liefert die im aktuellen Zustand erlaubten Links mit, der Client kennt URL-Strukturen nicht selbst.
2Ist HATEOAS für jede API sinnvoll?
Nein, bei einfachen CRUD-Endpunkten überwiegt der Aufwand. Sinnvoll vor allem bei Workflows mit echten Zustandsübergängen.
3Was ist HAL?
Ein leichtgewichtiges JSON-Format mit _links und _embedded, weit verbreitet und einfach in bestehende APIs zu integrieren.
4Wie werden Links zustandsabhängig erzeugt?
Eine Domain-Klasse liefert erlaubte Übergänge für den aktuellen Status, die Serialisierung erzeugt nur dafür Links.
5Ersetzt HATEOAS Versionierung?
Nein, es reduziert Kopplung bei Workflow-Änderungen, ersetzt aber keine Versionierungsstrategie für Datenfelder.
6Wie N+1-Probleme vermeiden?
Verwandte Ressourcen für die ganze Liste vorladen statt pro Element einzeln, bevor die HAL-Serialisierung läuft.
7Sollten Links je nach Rolle variieren?
Ja, Berechtigung sollte in die Link-Generierung einfließen, damit unterschiedliche Nutzerrollen unterschiedliche Aktionen sehen.
8Alternativen zu HAL?
JSON:API und Siren sind mächtiger, aber aufwendiger. HAL bleibt für die meisten PHP-Projekte der pragmatischste Einstieg.
9Wie strukturiert man einen Link-Builder?
Eine zentrale Klasse kennt die Basis-URL und erzeugt konsistente Link-Arrays, sodass URL-Änderungen nur an einer Stelle gepflegt werden.
10Beeinträchtigt HATEOAS die Performance?
Bei einzelnen Ressourcen kaum, bei Listen sollte die Zustandsprüfung gebündelt statt pro Element einzeln erfolgen.