API Platform 3 auf 4: der State-basierte Ablauf, RFC-7807-Fehler und Hydra-Änderungen
Die Kompatibilitäts-Flags von 3.4 einzeln umstellen, weg von den Event-Listenern, das neue Fehlerformat und die Hydra-Keys ohne Präfix übernehmen, dann auf 4.x wechseln.
Veröffentlicht 2026-09-12. Geschrieben für Teams, die das Upgrade selbst machen; wer es lieber machen lässt, findet hier Upgrades zum Festpreis. Auch verfügbar auf English.
Was 4.0 entfernt hat
API Platform 4 ist die Version, in der die 2.x-Kompatibilitätsschichten endgültig verschwinden. Der Event-Listener-basierte Request-Ablauf, der Legacy-Inflector, das Fehlerformat vor RFC 7807 und die hydra:-präfixierten Collection-Keys waren in 3.x alle umschaltbar und sind in 4 weg oder standardmäßig aus. 4.x setzt Symfony 6.4 oder neuer und PHP 8.2 oder neuer voraus; 4.3 ist der gepflegte Bugfix-Zweig und 4.2 der Security-Zweig. Alles darunter ist nicht mehr gepflegt.
Die Methode ist dieselbe wie bei 2 auf 3: auf das letzte 3.x (3.4) gehen, jedes Kompatibilitäts-Flag einzeln auf seinen 4.x-Wert stellen, reparieren, was bricht, dann springen. Jedes Flag ist ein Pull Request. Nichts hier braucht ein Big-Bang-Release.
Schritt 1: 3.4 und null Deprecations
composer require api-platform/core:^3.4 --with-all-dependencies
Testsuite mit der PHPUnit-Bridge ausführen (SYMFONY_DEPRECATIONS_HELPER=max[self]=0). API Platform 3.4 deprecated alles, was 4.0 entfernt; der Report ist Ihre Liste.
Schritt 2: der State-basierte Request-Ablauf
In 2.x und frühem 3.x lief ein Request durch eine Kette von Symfony-Event-Listenern (read, deserialize, validate, write, serialize) mit festen Prioritäten, und Projekte hängten ihre Logik an EventPriorities::PRE_WRITE und Co. 3.2 führte den State-basierten Ablauf ein, in dem dieselben Schritte Provider und Processor sind, die um Ihre Operation herum komponiert werden, und 4.0 macht ihn zum einzigen Ablauf.
# config/packages/api_platform.yaml (3.2+)
api_platform:
use_symfony_listeners: false # 4.x-Default; true behält vorerst die alten Listener
Umstellen und Tests ausführen. Jeder Listener, den Ihr Projekt auf den Events kernel.view oder kernel.request mit API-Platform-Prioritäten registriert hat, braucht jetzt ein neues Zuhause:
// Vorher: Listener auf PRE_WRITE
final class OrderSubmitListener implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [KernelEvents::VIEW => ['onView', EventPriorities::PRE_WRITE]];
}
public function onView(ViewEvent $event): void
{
$order = $event->getControllerResult();
if (!$order instanceof Order || !$event->getRequest()->isMethod('POST')) {
return;
}
$order->submittedAt = new \DateTimeImmutable();
}
}
// Nachher: ein Processor, der den Persist-Processor für diese Operation dekoriert
#[Post(processor: OrderSubmitProcessor::class)]
class Order {}
final class OrderSubmitProcessor implements ProcessorInterface
{
public function __construct(
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')] private ProcessorInterface $persist,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
{
$data->submittedAt = new \DateTimeImmutable();
return $this->persist->process($data, $operation, $uriVariables, $context);
}
}
Listener, die die Response berührten (Header hinzufügen, Ausgabe wrappen), werden zu einem Processor nach der Serialisierung oder zu einem normalen Symfony-Response-Listener ohne API-Platform-Prioritäten. Listener, die Autorisierung machten, sollten längst security:-Expressions oder Voter sein; falls nicht, ist jetzt der Moment.
Schritt 3: RFC-7807-Fehler
4.x liefert Fehler als application/problem+json (und die JSON-LD- und Hydra-Varianten) mit type, title, status, detail. Validierungsfehler tragen ein violations-Array. Zuerst auf 3.x einschalten:
api_platform:
defaults:
rfc_7807_compliant_errors: true
Was sich für Clients ändert: Der @type eines Fehlers ist Error oder ConstraintViolation (vorher hydra:Error und ConstraintViolationList), hydra:description wird zu detail, und der HTTP-Status eines Validierungsfehlers bleibt 422. Eigene Exception-zu-Status-Zuordnungen in api_platform.exception_to_status funktionieren weiter. Wer einen eigenen Error-Normalizer hatte, kann ihn fast sicher durch eine Resource im Stil von ErrorResource oder durch ProblemExceptionInterface auf den eigenen Exceptions ersetzen.
final class InsufficientStockException extends \RuntimeException implements ProblemExceptionInterface
{
public function getType(): string { return '/errors/insufficient-stock'; }
public function getTitle(): ?string { return 'Insufficient stock'; }
public function getStatus(): ?int { return 409; }
public function getDetail(): ?string { return $this->getMessage(); }
public function getInstance(): ?string { return null; }
}
Schritt 4: Hydra-Keys ohne Präfix
Collections serialisieren in 4.x als member, totalItems, view und search statt hydra:member, hydra:totalItems und so weiter. Der @context deklariert weiterhin das Hydra-Vokabular, JSON-LD-Konsumenten sind also nicht betroffen; reine JSON-Konsumenten, die hydra:member fest verdrahtet haben, schon.
api_platform:
serializer:
hydra_prefix: false # 4.x-Default; auf true setzen, um die alten Keys während eines Übergangs zu behalten
Auf 3.4 umstellen, Frontend und API-Tests anpassen, ausliefern. Wenn Ihre API externe Konsumenten hat, die Sie nicht kontrollieren, behalten Sie hydra_prefix: true in 4.x so lange wie nötig; die Option gibt es dort weiterhin.
Schritt 5: die kleineren Flags
| Option (3.x) | 4.x | Bedeutung |
|---|---|---|
keep_legacy_inflector: true | false / entfernt | Resource-Kurznamen werden mit symfony/string pluralisiert. Eine Resource namens Person war in beiden /people, aber unregelmäßige Namen können sich ändern; debug:router prüfen. |
event_listeners_backward_compatibility_layer | entfernt | 2.x-Listener-Verhalten; durch Schritt 2 abgelöst. |
enable_docs, enable_entrypoint, enable_swagger_ui | unverändert | Gibt es weiterhin. |
defaults.stateless: true | unverändert | Behalten; das schaltet sessionbasiertes Firewall-Verhalten für API-Routen ab. |
Schritt 6: Packages und Namespaces
4.x hat den Monolithen in Komponenten aufgeteilt. api-platform/core installiert weiterhin alles, und dabei zu bleiben ist in Ordnung. Wer den kleineren Fußabdruck will: Die Symfony-Integration ist api-platform/symfony plus die genutzten Teile (api-platform/doctrine-orm, api-platform/graphql, api-platform/openapi). Namespaces für Anwendungscode sind nicht umgezogen: ApiPlatform\Metadata\*, ApiPlatform\State\* und ApiPlatform\Doctrine\Orm\Filter\* sind in 3.4 und 4.3 gleich.
Doctrine ORM 3 wird von 4.x unterstützt (jedes 3.x außer exakt 3.0.0, das ausgeschlossen ist). GraphQL wechselt auf webonyx/graphql-php 15; eigene GraphQL-Resolver, die QueryItemResolverInterface implementieren, funktionieren weiter.
Schritt 7: Sprung
composer require api-platform/core:^4.3 --with-all-dependencies
bin/console cache:clear
bin/console debug:router | grep api_ # gegen einen Dump von vorher vergleichen
vendor/bin/phpunit
Entfernen Sie die in Schritt 2 bis 5 gesetzten Flags aus der Konfiguration, sie sind jetzt die Defaults. Erzeugen Sie das OpenAPI-Dokument neu (bin/console api:openapi:export --yaml) und vergleichen Sie es mit dem Export aus 3.4: Das fängt umbenannte Operationen und geänderte Fehlerschemata ab, bevor ein Client es tut.
Reihenfolge der Pull Requests
- 3.4, Deprecations auf null
use_symfony_listeners: false, Listener als Provider/Processor umgeschriebenrfc_7807_compliant_errors: true, Clients und Tests angepassthydra_prefix: false, Frontend angepasst- Inflector und restliche Flags
- Sprung auf 4.3, OpenAPI-Diff
Zusätzlich zu einem Symfony-Upgrade landet das für die meisten APIs bei €3.000 bis €6.000; Treiber sind die Zahl der eigenen Listener und der Konsumenten, die Fehlerbodies parsen.
Noch auf API Platform 2 oder 3?
Der Scan markiert API Platform unter 4 als Cliff, zeigt, welche Symfony-Versionen Ihr installiertes Release erlaubt, und ordnet die API-Platform-Schritte korrekt relativ zu den Symfony- und Doctrine-Schritten ein.
Weitere Leitfäden
- Upgrade von Symfony 5.4 auf 6.4: der vollständige Leitfaden
- Upgrade von Symfony 6.4 auf 7.4: Deprecations, Return Types und der Sprung
- API Platform 2.6 auf 3: Resources, Data Provider und Persister migrieren
- Upgrade von Doctrine ORM 2 auf 3 (und DBAL 3 auf 4) in einer Symfony-Anwendung
- Verwaiste Symfony-Bundles ersetzen: SensioFrameworkExtra, Guard, Swiftmailer, FOSRest, FOSUser