Symfony OpenAPI: Vom Contract-First-Design zur getesteten, dokumentierten API
Die meisten Symfony-Teams schreiben zuerst die API und danach die Dokumentation, meist als Nachgedanke, sobald jemand im Frontend fragt, was ein Feld eigentlich zurückgibt. Ein Symfony-OpenAPI-Workflow dreht diese Reihenfolge um. Du schreibst die OpenAPI-Spezifikation, bevor du auch nur einen Controller anfasst, und die Spezifikation wird zu dem Ding, über das beide Seiten des Projekts streiten und sich einigen, statt zu einem PDF, das nach dem Kickoff-Meeting niemand mehr öffnet.
Dieser Beitrag deckt den vollständigen Workflow ab: den Contract zuerst entwerfen, daraus Symfony-Scaffolding generieren, und die Prüfungen einrichten, die verhindern, dass die Implementierung von dem abweicht, was die Spezifikation verspricht. Wenn dein Team bereits eine funktionierende API hat und nachträglich Dokumentation aufsetzen möchte, deckt die zweite Hälfte auch diesen Weg ab.
Warum Contract-First statt Code-First
Code-First-API-Dokumentation, bei der du PHP-Klassen annotierst und daraus eine Spezifikation generierst, ist der verbreitetere Ansatz im Symfony-Ökosystem, und für kleine Projekte ist daran nichts falsch. Das Problem taucht auf, sobald mehr als ein Team von der API abhängt. Ein Frontend-Team, ein Mobile-Team und eine Partner-Integration müssen alle wissen, wie die API aussieht, bevor sie gebaut wird, nicht danach. Wenn die Spezifikation aus Code generiert wird, den es noch nicht gibt, haben sie nichts, wogegen sie bauen können.
Contract-First-Design löst das, indem es das OpenAPI-Dokument von Tag eins an zur Quelle der Wahrheit macht. Das Backend-Team entwirft die Spezifikation, holt sich die Zustimmung der Konsumenten, und beginnt erst dann mit der Implementierung. Jeder nachgelagerte Beteiligte kann mit Mock-Servern gegen den Contract bauen, während die echten Endpunkte noch in Arbeit sind.
Die Spezifikation entwerfen, bevor du Symfony anfasst
Beginne in einem dedizierten Design-Tool statt in einem Texteditor. Sowohl Stoplight als auch Redocly geben dir einen visuellen Editor für OpenAPI-3.1-Dokumente, Inline-Validierung während der Eingabe, und einen Weg, die generierte Dokumentation sofort in der Vorschau zu sehen. Beide fangen strukturelle Fehler ab, wie ein fehlendes Pflichtfeld oder ein Response-Schema, das nicht zum Request-Schema passt, weit bevor sie einen Pull Request erreichen.
Behandle die Spezifikation in dieser Phase wie eine Datenbank-Migration: etwas, das andere Leute prüfen müssen, bevor es gemergt wird. Verteile den Entwurf an die Teams, die die API konsumieren werden. Ein Mobile-Team, das ein Paginierungsschema entdeckt, mit dem es nicht arbeiten kann, ist in einem Design-Tool deutlich günstiger zu korrigieren als in einem ausgelieferten Endpunkt.
Sobald sich die Spezifikation stabilisiert hat, exportiere das OpenAPI-3.1-YAML oder -JSON und committe es ins Repository. Diese Datei muss jetzt den Kontakt mit Symfony überstehen, ohne still zu veralten, und genau darum geht es im Rest des Workflows.
Symfony-Route-Stubs aus der Spezifikation generieren
Mit einer validierten Spezifikation in der Hand kann die openapi-generator-Toolchain PHP-Scaffolding erzeugen: Controller-Stubs, Route-Definitionen sowie Request- und Response-DTOs, die exakt zum Schema passen. Das erspart den mühsamen Teil, eine Spezifikation von Hand in Symfony-Konventionen zu übersetzen, und es bedeutet, dass generierter Code und Spezifikation von Anfang an übereinstimmen.
Generierte Stubs sind ein Ausgangspunkt, kein fertiges Feature. Du wirst trotzdem die Geschäftslogik schreiben, die Doctrine-Repositories anbinden und die Authentifizierung handhaben. Halte die generierte Schicht dünn: ein Controller, der den eingehenden Request gegen das Schema validiert und an eine Service-Klasse übergibt, die der Generator nie anfasst. Diese Trennung ist später wichtig, weil das Neugenerieren von Stubs nach einer Spezifikationsänderung nicht riskieren sollte, von Hand geschriebene Logik zu überschreiben.
Spezifikation und Implementierung ehrlich halten mit openapi-psr7-validator
Das eigentliche Risiko in einem Contract-First-Setup ist nicht die anfängliche Generierung, sondern die langsame Drift danach. Jemand fügt ein Feld zur Response hinzu und vergisst, die Spezifikation zu aktualisieren. Jemand benennt einen Query-Parameter im Controller um, aber nicht in der YAML. Sechs Monate später ist die eine Quelle der Wahrheit still falsch geworden.
Die Bibliothek openapi-psr7-validator schließt diese Lücke, indem sie echte HTTP-Requests und -Responses zur Laufzeit gegen das OpenAPI-Schema validiert, über Symfonys PSR-7-Bridge. Bau sie in deine funktionale Test-Suite ein, damit jeder Test, der einen echten Endpunkt trifft, auch prüft, dass Request und Response zur Spezifikation passen. Wenn ein Entwickler ein neues Feld zu einer Response hinzufügt, ohne das Schema zu aktualisieren, schlägt der Test sofort fehl, mit einer Meldung, die genau auf die vertragswidrige Eigenschaft zeigt, statt drei Sprints später als verwirrter Bug-Report eines Konsumenten aufzutauchen.
Eine Spezifikation nachträglich auf bestehenden Symfony-Code aufsetzen
Wenn du nicht bei null anfängst, und die meisten Teams tun das nicht, geht NelmioApiDocBundle den umgekehrten Weg: Statt Code aus einer Spezifikation zu generieren, generiert es eine OpenAPI-Spezifikation aus PHP-Attributen, die bereits auf deinen Controllern und DTOs vorhanden sind. Das ist der praktische Einstiegspunkt für Teams, deren API ganz ohne Contract gebaut wurde.
Das Bundle liest deine bestehenden Route-Definitionen, Request-Klassen und Response-Serialisierung, um eine Spezifikation zu erzeugen, die widerspiegelt, was die API heute tatsächlich tut, nicht was sie tun sollte. Diese erste generierte Spezifikation ist selten sauber. Rechne damit, Zeit damit zu verbringen, fehlende Beschreibungen zu ergänzen, nie korrekt dokumentierte Response-Codes zu korrigieren und zu locker gelassene Schemas zu verschärfen, etwa einen Endpunkt, der als generisches Objekt typisiert ist statt als die tatsächliche Form der Daten.
Sobald diese erste Spezifikation generiert und bereinigt ist, validiere sie gegen echte Konsumenten, bevor du sie als maßgeblich behandelst. Führe dieselben Requests aus, die dein Frontend oder deine Partner-Integrationen bereits machen, und prüfe sie mit openapi-psr7-validator gegen das neue Schema. Überall dort, wo der Validator eine Abweichung meldet, hast du entweder einen Bug in der bestehenden API oder eine Lücke in der neu generierten Spezifikation gefunden, und in beiden Fällen braucht es eine Entscheidung, bevor die Spezifikation zum Referenzdokument wird, mit dem alle arbeiten.
Die Dokumentation bereitstellen
Eine Spezifikation, die nur in einem Git-Repository existiert, hilft niemandem außerhalb des Backend-Teams. Sowohl Swagger UI als auch Redoc können ein OpenAPI-Dokument als durchsuchbare, interaktive Referenz rendern, und beide lassen sich sauber in eine Symfony-Anwendung integrieren: die rohe Spezifikation unter einer Route wie /api/docs.json ausliefern, dann einen der beiden Renderer darauf zeigen lassen.
Swagger UI neigt zur interaktiven Erkundung und lässt einen Entwickler Testrequests direkt aus dem Browser gegen eine laufende Instanz senden. Redoc erzeugt ein klareres, leseorientiertes Layout, das als Referenz für Partner besser funktioniert, die nur etwas nachschlagen müssen. Manche Teams betreiben beide, Swagger UI für die interne Entwicklung und Redoc für die Version, die mit externen Integratoren geteilt wird.
Die Spezifikation zusammen mit dem Code versionieren
Das OpenAPI-Dokument sollte im selben Repository wie die Symfony-Anwendung liegen und durch dieselben Branches und Pull Requests laufen wie jede andere Code-Änderung. Es als separates Artefakt zu behandeln, das nach eigenem Zeitplan gepflegt wird, ist der Weg, wie Spezifikationen innerhalb weniger Wochen veralten.
Wenn die API eine Breaking Change einführt, versioniere die Spezifikation genauso, wie du die API selbst versionierst, ob das nun ein Pfad-Präfix, ein Header oder ein anderes Schema ist, das dein Team bereits nutzt. Die Spezifikation für jede unterstützte Version sollte einzeln abrufbar sein, damit ein Konsument auf einer älteren Version nicht auf Dokumentation für Endpunkte schaut, die er noch nicht aufrufen kann.
Der CI-Check, der das System ehrlich hält
Nichts von dem oben Genannten hält ohne Durchsetzung. Füge einen CI-Schritt hinzu, der die openapi-psr7-validator-Checks bei jedem Pull Request gegen deine funktionale Test-Suite laufen lässt, und lass den Build fehlschlagen, wenn irgendein Request oder Response nicht zur Spezifikation passt. Das ist der Schritt, der aus der Idee einer Spezifikation als Quelle der Wahrheit etwas tatsächlich Wahres macht.
Die Regel ist einfach: Kein Pull Request wird gemergt, wenn Implementierung und Spezifikation nicht übereinstimmen. Das schließt Pull Requests ein, die nur die Spezifikation anfassen, denn eine Schemaänderung ohne entsprechendes Implementierungs-Update ist genauso eine Form von Drift wie umgekehrt. Teams, die diesen Schritt überspringen, landen tendenziell wieder dort, wo sie angefangen haben: bei einem Dokument, das am Tag seiner Entstehung korrekt war und jeden Tag danach zunehmend fiktiv wird.
Wo das in ein größeres Symfony-Projekt passt
Ein Contract-First-OpenAPI-Setup ist ein Teil einer größeren Disziplin rund um Code-Quality-Beratung, der Art von Review, die Drift, fehlende Tests und architektonische Abkürzungen erkennt, bevor sie teuer werden. Wenn du das auf einer neuen Symfony-Anwendung aufbaust, hängt es außerdem mit Entscheidungen zusammen, die unter Custom Software Development fallen, insbesondere wie viel API-Oberfläche du welchen Konsumenten gegenüber offenlegst.
Wenn dein Team eine Symfony-API hat, die organisch gewachsen ist und jetzt einen echten Contract nachträglich braucht, oder du eine neue API startest und das Contract-First-Setup gleich richtig aufsetzen willst, melde dich unter hello@wolf-tech.io oder schau auf wolf-tech.io vorbei. Wir haben beides gemacht, und das Zweite ist immer weniger Arbeit, als es von außen aussieht.

