Stärken und blinde Flecken der automatischen Spec-Generierung
API Platform generiert OpenAPI-Dokumentation aus PHP-Klassen – das spart Stunden manueller Arbeit. Aber Schema-Namen mit internen Zählern, fehlende Beispiele, rudimentäre Security-Definitionen und nicht dokumentierte Fehlerfälle zeigen, wo das Automatismus-Versprechen seine Grenzen hat. Dieser Artikel zeigt konkret, was gut funktioniert – und was überschrieben werden muss.
Inhaltsverzeichnis
- 1. Was API Platform automatisch gut macht
- 2. Schema-Namen: Interne Zähler und generische Bezeichner bereinigen
- 3. Response-Beispiele: Warum sie fehlen und wie man sie ergänzt
- 4. Fehlerdokumentation: 4xx und 5xx vollständig beschreiben
- 5. Security-Definitionen: Bearer und OAuth2 korrekt konfigurieren
- 6. OpenApiFactory: Vollständige Kontrolle über die Spec
- 7. Serializer-Gruppen und ihr Einfluss auf Schemas
- 8. Automatisch vs. manuell: Direkter Vergleich
- 9. Zusammenfassung
- 10. FAQ
1. Was API Platform automatisch gut macht
API Platform löst das zentrale Problem der API-Dokumentation: Sie bleibt synchron mit dem Code. Wer ein neues PHP-Attribut an einer Resource-Klasse hinzufügt oder einen neuen Endpoint über #[ApiResource] deklariert, sieht die Änderung sofort in der generierten OpenAPI-Spezifikation. Es gibt keine separate Dokumentationsdatei, die vergessen werden könnte. Diese automatische Synchronität ist der entscheidende Vorteil gegenüber manuell gepflegten OpenAPI-Dateien.
Konkret generiert API Platform automatisch korrekt: alle CRUD-Endpoints mit den richtigen HTTP-Methoden, Request-Body-Schemas aus den deklarierten Properties, Pflichtfeld-Markierungen aus PHPDoc und Validierungsattributen, Content-Type-Header und die Swagger-UI für interaktive Tests. Für einfache CRUD-APIs ist die automatische Dokumentation oft ausreichend. Die Probleme beginnen, wenn die API komplexer wird: verschiedene Antwortstrukturen je nach Kontext, angepasste Feldnamen, Authentifizierungsflows und präzise Fehlerdokumentation.
2. Schema-Namen: Interne Zähler und generische Bezeichner bereinigen
Das erste Problem, das Entwickler mit API Platform und OpenAPI erleben, sind die automatisch generierten Schema-Namen. API Platform erzeugt Namen wie User.jsonld, User-user.read, User-user.write oder gar User-read.1 mit angehängten Zählern. Diese Namen sind für Entwickler und Code-Generatoren unlesbar. Frontend-Teams, die TypeScript-Clients aus der Spec generieren, erhalten Typenamen wie UserJsonld oder UserRead1 – unpraktikabel in jedem großen Projekt.
Die Lösung liegt in expliziten Schema-Namen via Attributen. Mit #[ApiResource(shortName: 'User')] kann der Basisname gesetzt werden. Für Serializer-Gruppen bietet API Platform seit Version 3.1 den openapi-Parameter in #[ApiResource], über den Schema-Namen für Lese- und Schreiboperationen explizit definiert werden können. Für komplexe Fälle ist die Implementierung eines eigenen OpenApiFactoryInterface-Decorators der direkteste Weg: Der Decorator greift in die generierte Spec ein und benennt Schemas nach einer definierten Konvention um.
<?php
// src/ApiResource/UserResource.php
declare(strict_types=1);
namespace App\ApiResource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\OpenApi\Model;
use Symfony\Component\Serializer\Annotation\Groups;
#[ApiResource(
shortName: 'User',
operations: [
new Get(
uriTemplate: '/users/{id}',
openapi: new Model\Operation(
summary: 'Retrieve a single user',
description: 'Returns a user by ID. Requires authentication.',
tags: ['Users'],
)
),
new GetCollection(
uriTemplate: '/users',
openapi: new Model\Operation(
summary: 'List all users',
tags: ['Users'],
)
),
new Post(
uriTemplate: '/users',
openapi: new Model\Operation(
summary: 'Create a new user',
tags: ['Users'],
)
),
],
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']],
)]
class UserResource
{
public ?int $id = null;
#[Groups(['user:read', 'user:write'])]
public string $email = '';
#[Groups(['user:read', 'user:write'])]
public string $name = '';
#[Groups(['user:read'])]
public string $role = 'viewer';
#[Groups(['user:read'])]
public bool $active = true;
#[Groups(['user:read'])]
public \DateTimeImmutable $createdAt;
}
3. Response-Beispiele: Warum sie fehlen und wie man sie ergänzt
API Platform generiert automatisch Request- und Response-Schemas, aber keine examples-Blöcke in der OpenAPI-Spezifikation. Das ist ein kritisches Manko, weil Beispiele die Grundlage für Mock-Server (Prism, WireMock) und interaktive Dokumentation (Swagger UI, Redoc) sind. Ohne Beispiele können Frontend-Entwickler keinen Mock-Server nutzen, und die Swagger UI zeigt nur das abstrakte Schema, nicht ein konkretes Beispiel-JSON.
Beispiele können auf zwei Wegen ergänzt werden: Über #[ApiProperty]-Attribute auf einzelnen Feldern mit dem openapiContext-Parameter oder über den OpenApiFactory-Decorator auf Operationsebene. Letzteres ist mächtiger, weil es vollständige Beispiel-Objekte definiert statt einzelner Feldwerte. Der Factory-Decorator iteriert über alle Operationen in der generierten Spec und ergänzt Beispiele nach Bedarf – ohne die bestehende Generierungslogik zu ersetzen.
<?php
// src/OpenApi/OpenApiFactory.php
declare(strict_types=1);
namespace App\OpenApi;
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\Model;
use ApiPlatform\OpenApi\OpenApi;
/**
* Decorator that adds examples, security schemes and error documentation
* to the automatically generated OpenAPI specification.
*/
final class OpenApiFactory implements OpenApiFactoryInterface
{
public function __construct(
private readonly OpenApiFactoryInterface $decorated,
) {}
public function __invoke(array $context = []): OpenApi
{
$openApi = ($this->decorated)($context);
$this->addUserExamples($openApi);
$this->addSecuritySchemes($openApi);
$this->addGlobalErrorResponses($openApi);
return $openApi;
}
private function addUserExamples(OpenApi $openApi): void
{
$paths = $openApi->getPaths();
foreach ($paths->getPaths() as $path => $pathItem) {
if (!str_starts_with($path, '/users')) {
continue;
}
$getOperation = $pathItem->getGet();
if ($getOperation === null) {
continue;
}
// Add example to 200 response
$responses = $getOperation->getResponses();
$okResponse = $responses['200'] ?? null;
if ($okResponse instanceof Model\Response) {
$content = $okResponse->getContent();
$jsonContent = $content['application/json'] ?? null;
if ($jsonContent instanceof Model\MediaType) {
$updatedContent = $jsonContent->withExample(
new \ArrayObject([
'id' => 42,
'email' => 'user@example.com',
'name' => 'Maria Mustermann',
'role' => 'editor',
'active' => true,
'createdAt' => '2025-01-15T10:30:00Z',
])
);
// Replace in path item...
}
}
}
}
private function addSecuritySchemes(OpenApi $openApi): void
{
$components = $openApi->getComponents();
$securitySchemes = $components->getSecuritySchemes() ?? new \ArrayObject();
$securitySchemes['bearerAuth'] = new \ArrayObject([
'type' => 'http',
'scheme' => 'bearer',
'bearerFormat' => 'JWT',
'description' => 'JWT Bearer Token. Obtain via POST /auth/token.',
]);
$openApi->withComponents($components->withSecuritySchemes($securitySchemes));
}
private function addGlobalErrorResponses(OpenApi $openApi): void
{
// Add 401 and 403 to all protected endpoints
// Implementation iterates all paths and adds standard error responses
}
}
4. Fehlerdokumentation: 4xx und 5xx vollständig beschreiben
API Platform dokumentiert automatisch nur wenige Fehlerfälle: einen generischen 400 für Validierungsfehler und einen 404, wenn eine Resource nicht gefunden wird. Für eine produktionsreife API reicht das nicht aus. Jeder Endpoint braucht eine vollständige Dokumentation aller möglichen Fehlercodes: 401 für nicht authentifizierte Anfragen, 403 für fehlende Berechtigungen, 409 für Konflikte, 422 für Validierungsfehler mit Felddetails, 429 für Rate-Limiting und 503 für temporäre Nichtverfügbarkeit.
Diese Fehlerfälle sind nicht nur für die menschliche Dokumentation wichtig – sie sind auch die Basis dafür, dass Mock-Server und Contract-Tests alle Fehlerpfade abdecken können. Im OpenApiFactory-Decorator werden globale Fehlerresponses einmal definiert und allen geschützten Endpoints zugewiesen. Das components/responses-Objekt in der Spec nimmt Referenzen auf wiederverwendbare Response-Definitionen auf, die über $ref: '#/components/responses/Unauthorized' in jedem Endpoint referenziert werden.
5. Security-Definitionen: Bearer und OAuth2 korrekt konfigurieren
Die Security-Definitionen von API Platform sind minimal: Wenn JWT-Bundle konfiguriert ist, erscheint ein JWT-Security-Schema in der Spec – aber ohne bearerFormat, ohne Beschreibung und ohne Angabe, wie der Token zu erhalten ist. Für einen vollständigen Security-Kontext braucht die Spec ein oder mehrere Security-Schemes mit allen Parametern, einen Endpoint zur Token-Generierung (POST /auth/token) und klare Angaben, welche Operationen welches Security-Scheme erfordern.
OAuth2-Flows werden automatisch nicht generiert. Wer OAuth2 mit Authorization Code Flow, Client Credentials oder Password Grant nutzt, muss das vollständige oauth2-Security-Schema manuell im OpenApiFactory-Decorator ergänzen. Das umfasst authorizationUrl, tokenUrl, refreshUrl und alle scopes mit Beschreibungen. Swagger UI und Redoc rendern diese Informationen als vollständige OAuth2-Flow-Dokumentation mit interaktiver Authentifizierungsmöglichkeit.
# config/packages/api_platform.yaml
api_platform:
title: 'Mironsoft API'
version: '2.0.0'
description: |
REST API for Mironsoft platform services.
## Authentication
All endpoints require a valid Bearer token obtained via `POST /auth/token`.
## Rate Limiting
Requests are limited to 1000/hour per API key. See `X-RateLimit-*` headers.
openapi:
# Additional security schemes added via OpenApiFactory decorator
api_keys:
# Empty here — configured programmatically for full control
formats:
json: ['application/json']
jsonld: ['application/ld+json']
# Disable Hydra context for pure JSON APIs
defaults:
formats: ['json']
input_formats: ['json']
# services.yaml
services:
App\OpenApi\OpenApiFactory:
decorates: 'api_platform.openapi.factory'
arguments:
- '@.inner'
tags: []
6. OpenApiFactory: Vollständige Kontrolle über die Spec
Der OpenApiFactory-Decorator ist das zentrale Werkzeug für alle manuellen Anpassungen an der automatisch generierten Spec. Er implementiert das OpenApiFactoryInterface, delegiert die eigentliche Generierung an den dekorierten Factory und greift dann gezielt in das Ergebnis ein. Das Muster ist präzise: Automatisch generieren lassen, dann nur das Notwendige überschreiben. Das minimiert den Pflegeaufwand, weil neue Endpoints automatisch in der Spec erscheinen und nur die spezifischen Ergänzungen manuell bleiben.
Typische Aufgaben für den Decorator: Schema-Namen normalisieren, Beispiele zu Responses ergänzen, Security-Schemes und globale Security-Requirements setzen, wiederverwendbare Fehlerresponses in components/responses registrieren und deprecierte Endpoints markieren. Der Decorator kann auch externe YAML-Dateien einlesen und mit der generierten Spec zusammenführen – das ermöglicht, komplexe Schemas oder Beispiele separat zu pflegen und automatisch zu integrieren.
7. Serializer-Gruppen und ihr Einfluss auf Schemas
Symfony Serializer-Gruppen bestimmen, welche Felder in welchem Kontext serialisiert werden. API Platform übernimmt diese Gruppen und generiert für jede Kombination aus Endpoint und Serializer-Gruppe ein eigenes Schema. Das Ergebnis: Statt eines User-Schemas gibt es User.jsonld-user.read, User-user.write, User.jsonld-user.admin und so weiter. Frontend-Teams, die TypeScript-Typen generieren, erhalten einen schwer navigierbaren Typ-Zoo.
Die Empfehlung: Serializer-Gruppen sinnvoll begrenzen. Für die meisten APIs reichen zwei Gruppen pro Resource: eine Lesegruppe (user:read) und eine Schreibgruppe (user:write). Wenn Admin-Endpoints andere Felder zeigen als normale Benutzer-Endpoints, ist eine separate ApiResource-Klasse für den Admin-Kontext sauberer als eine dritte Serializer-Gruppe. Das reduziert die Anzahl generierter Schemas und macht die Spec navigierbar.
8. Automatisch vs. manuell: Direkter Vergleich
Der Überblick zeigt, wo die automatische Generierung von API Platform ausreicht und wo manueller Eingriff notwendig ist. Diese Linie ist in jedem Projekt anders – einfache CRUD-APIs brauchen weniger manuelle Arbeit als komplexe, mehrstufig authentifizierte APIs mit verschiedenen Client-Rollen.
| Feature | Automatisch | Manuell nötig | Tool |
|---|---|---|---|
| Endpoint-Generierung | Vollständig aus Attributen | — | #[ApiResource] |
| Schema-Namen | Interne Zähler, unleserlich | shortName + Decorator | OpenApiFactory |
| Response-Beispiele | Fehlen komplett | Pflicht im Decorator | OpenApiFactory |
| Validierungsfehler | Generisch dokumentiert | Fehlerschema ergänzen | #[ApiProperty] |
| Security-Schemes | Minimal, ohne Detail | bearerFormat, OAuth2 | OpenApiFactory |
Die Faustregel: Alles, was strukturell durch PHP-Klassen ausgedrückt werden kann (Felder, Typen, Validierung), generiert API Platform gut. Alles, was über Struktur hinausgeht (Semantik, Beispiele, Security-Flows, Fehlerbeschreibungen), braucht den manuellen Eingriff über den Decorator. Das Muster ist konsistent: Code-Struktur → automatisch, Dokumentationsqualität → manuell.
Mironsoft
API Platform, Symfony und OpenAPI-Consulting
API Platform OpenAPI-Dokumentation optimieren?
Wir analysieren eure API Platform-Konfiguration, implementieren einen OpenApiFactory-Decorator und bringen die generierte Spec auf Produktionsqualität – mit Beispielen, Security und vollständiger Fehlerdokumentation.
Spec-Audit
Analyse der aktuellen generierten OpenAPI-Spec auf Qualitätsprobleme
Decorator-Implementierung
OpenApiFactory mit Beispielen, Security und Fehlerresponses
Code-Generator-Setup
TypeScript-Client-Generierung aus der optimierten Spec
9. Zusammenfassung
API Platform generiert OpenAPI automatisch – das ist der größte Vorteil gegenüber manuellen Spezifikationen. Endpoint-Struktur, Request-Body-Schemas und Pflichtfelder sind immer synchron mit dem PHP-Code. Die Grenzen der Automatisierung sind klar: Schema-Namen brauchen explizite Konfiguration, Beispiele fehlen vollständig, Fehlerdokumentation ist rudimentär und Security-Schemes sind unvollständig.
Der OpenApiFactory-Decorator ist die saubere Lösung für alle manuellen Ergänzungen. Er greift nach der automatischen Generierung ein, ohne die bestehende Logik zu ersetzen. Neue Endpoints erscheinen weiterhin automatisch in der Spec und erhalten über den Decorator die notwendigen Beispiele und Fehlerresponses. Mit shortName auf #[ApiResource] und bewusst begrenzten Serializer-Gruppen entsteht eine navigierbare Spec, die als Basis für Mock-Server, Contract-Tests und TypeScript-Codegenerierung dient.
API Platform OpenAPI — Das Wichtigste auf einen Blick
Automatisch gut
Endpoint-Generierung, Request-Body-Schemas, Pflichtfelder, Content-Type. Immer synchron mit PHP-Code.
Manuell nötig
Schema-Namen (shortName), Response-Beispiele, 4xx/5xx-Fehlerdokumentation, vollständige Security-Schemes.
OpenApiFactory Decorator
Implementiert OpenApiFactoryInterface, delegiert an dekorierten Service, überschreibt gezielt. Als Symfony-Service deklarieren.
Serializer-Gruppen
Zwei Gruppen pro Resource (read/write) als Maximum. Mehr Gruppen = mehr Schemas = unleserliche Spec.