API Platform Symfony REST: Vollständiges Tutorial und Interview-Fragen 2026
REST APIs mit API Platform 4.3 und Symfony 7 entwickeln. State Providers, Processors, Serialisierungsgruppen und typische Interview-Fragen für Symfony-Entwickler.

API Platform 4.3 verwandelt Symfony in ein leistungsstarkes REST-API-Framework mit automatischer OpenAPI-Dokumentation, Content Negotiation und einer sauberen Architektur, die Lese- und Schreiboperationen trennt. Dieses Tutorial behandelt die Installation, fortgeschrittene Patterns und die Interview-Fragen, die Senior-Entwickler von Einsteigern unterscheiden.
API Platform 4 verwendet State Providers für GET-Operationen und State Processors für POST/PUT/PATCH/DELETE. Diese Trennung entspricht CQRS-Prinzipien und vereinfacht das Testen erheblich.
API Platform auf Symfony 7 installieren
Die Installation von API Platform erfordert Symfony 7.2 oder höher. Das Bundle integriert sich standardmäßig mit Doctrine ORM, unterstützt aber auch benutzerdefinierte Datenquellen über das Provider/Processor-Pattern.
# API Platform mit Symfony Flex installieren
composer require api
# Installation überprüfen
php bin/console debug:router | grep apiDas api-Recipe installiert api-platform/symfony zusammen mit den Serializer-, Validator- und Property-Access-Komponenten. Symfony Flex konfiguriert die Routen automatisch unter /api.
# config/packages/api_platform.yaml
api_platform:
title: 'My API'
version: '1.0.0'
formats:
jsonld: ['application/ld+json']
json: ['application/json']
docs_formats:
jsonld: ['application/ld+json']
jsonopenapi: ['application/vnd.openapi+json']
html: ['text/html']
defaults:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Accept-Language']Die Einstellung stateless: true deaktiviert PHP-Sessions für alle API-Endpunkte, reduziert den Speicherverbrauch und ermöglicht horizontale Skalierung.
Die erste API-Ressource mit Attributen erstellen
API Platform 4 verwendet PHP 8-Attribute zur Deklaration von API-Ressourcen. Jede Entity wird durch das #[ApiResource]-Attribut zu einem API-Endpunkt.
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ApiResource(
operations: [
new GetCollection(),
new Get(),
new Post(security: "is_granted('ROLE_ADMIN')"),
new Put(security: "is_granted('ROLE_ADMIN')"),
new Delete(security: "is_granted('ROLE_ADMIN')")
],
paginationItemsPerPage: 30
)]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
private string $name;
#[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
#[Assert\Positive]
private string $price;
#[ORM\Column]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getPrice(): string
{
return $this->price;
}
public function setPrice(string $price): static
{
$this->price = $price;
return $this;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
}Der security-Parameter jeder Operation nutzt Symfonys Expression Language. Die is_granted()-Funktion prüft Voter-Entscheidungen und ermöglicht rollenbasierte Zugriffskontrolle ohne benutzerdefinierte Controller.
Serialisierungsgruppen zur Response-Gestaltung
Serialisierungsgruppen steuern, welche Eigenschaften in API-Responses erscheinen. Verschiedene Operationen können unterschiedliche Felder derselben Entity anzeigen.
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity]
#[ApiResource(
operations: [
new GetCollection(normalizationContext: ['groups' => ['user:list']]),
new Get(normalizationContext: ['groups' => ['user:read']]),
new Post(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']]
)
]
)]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['user:list', 'user:read'])]
private ?int $id = null;
#[ORM\Column(length: 180)]
#[Groups(['user:list', 'user:read', 'user:write'])]
private string $email;
#[ORM\Column]
#[Groups(['user:write'])]
private string $password;
#[ORM\Column]
#[Groups(['user:read'])]
private \DateTimeImmutable $registeredAt;
#[ORM\Column(type: 'json')]
#[Groups(['user:read'])]
private array $roles = [];
}Der normalizationContext steuert die Ausgabe (PHP zu JSON). Der denormalizationContext steuert die Eingabe (JSON zu PHP). Das password-Feld verwendet nur user:write, sodass es niemals in Responses erscheint.
State Providers für benutzerdefinierte Datenquellen
State Providers rufen Daten für GET-Operationen ab. Die Standard-Provider ItemProvider und CollectionProvider verwenden Doctrine, aber benutzerdefinierte Provider ermöglichen beliebige Datenquellen: Elasticsearch, externe APIs oder berechnete Werte.
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Repository\ProductRepository;
use App\Dto\ProductStats;
final readonly class ProductStatsProvider implements ProviderInterface
{
public function __construct(
private ProductRepository $productRepository
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ProductStats
{
$totalProducts = $this->productRepository->count([]);
$averagePrice = $this->productRepository->getAveragePrice();
$lowStockCount = $this->productRepository->countLowStock(threshold: 10);
return new ProductStats(
totalProducts: $totalProducts,
averagePrice: $averagePrice,
lowStockCount: $lowStockCount
);
}
}namespace App\Dto;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use App\State\ProductStatsProvider;
#[ApiResource(
operations: [
new Get(
uriTemplate: '/products/stats',
provider: ProductStatsProvider::class
)
]
)]
final readonly class ProductStats
{
public function __construct(
public int $totalProducts,
public float $averagePrice,
public int $lowStockCount
) {}
}Das ProductStats-DTO (Data Transfer Object) ist keine Doctrine-Entity. Es repräsentiert berechnete Daten, die unter /api/products/stats verfügbar sind. Der Provider berechnet die Werte bei jeder Anfrage.
Bereit für deine Symfony-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
State Processors für Schreiboperationen
State Processors verarbeiten POST-, PUT-, PATCH- und DELETE-Operationen. Benutzerdefinierte Processors ermöglichen die Ausführung von Geschäftslogik vor oder nach der Persistierung.
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
final readonly class UserRegistrationProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
private UserPasswordHasherInterface $passwordHasher
) {}
public function process(
mixed $data,
Operation $operation,
array $uriVariables = [],
array $context = []
): User {
// $data ist die deserialisierte User-Entity aus dem Request-Body
$hashedPassword = $this->passwordHasher->hashPassword(
$data,
$data->getPlainPassword()
);
$data->setPassword($hashedPassword);
$data->eraseCredentials();
$this->entityManager->persist($data);
$this->entityManager->flush();
return $data;
}
}Der Processor wird an die POST-Operation der User-Entity angehängt:
#[ApiResource(
operations: [
new Post(
processor: UserRegistrationProcessor::class,
denormalizationContext: ['groups' => ['user:create']]
)
]
)]Der Processor hasht das Passwort, bevor Doctrine die Entity persistiert. Die plainPassword-Eigenschaft verwendet eine schreibgeschützte Serialisierungsgruppe und wird niemals in der Datenbank gespeichert.
Filter für Query-Parameter
API Platform bietet integrierte Filter zum Suchen, Sortieren und Filtern von Collections. Filter fügen Query-Parameter zu GET-Collection-Endpunkten hinzu.
namespace App\Entity;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
#[ORM\Entity]
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: [
'title' => 'partial',
'author.name' => 'exact',
'category' => 'exact'
])]
#[ApiFilter(DateFilter::class, properties: ['publishedAt'])]
#[ApiFilter(OrderFilter::class, properties: ['publishedAt', 'title'])]
class Article
{
// Entity-Eigenschaften...
}Die Filterkonfiguration aktiviert folgende Query-Parameter:
GET /api/articles?title=symfony(Teilübereinstimmung im Titel)GET /api/articles?author.name=John(exakte Übereinstimmung in verknüpfter Entity)GET /api/articles?publishedAt[after]=2026-01-01(Datumsbereich)GET /api/articles?order[publishedAt]=desc(Sortierung)
Benutzerdefinierte Filter erweitern AbstractFilter für komplexe Abfragelogik, die integrierte Filter nicht abdecken können.
Fehlerbehandlung und Validierung
API Platform integriert sich mit Symfony Validator. Validierungsfehler geben 422 Unprocessable Entity mit RFC 7807-Problemdetails zurück.
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ApiResource]
class Order
{
#[ORM\Column]
#[Assert\NotBlank(message: 'Bestellmenge ist erforderlich')]
#[Assert\Positive(message: 'Menge muss größer als null sein')]
#[Assert\LessThanOrEqual(value: 100, message: 'Maximal 100 Artikel pro Bestellung')]
private int $quantity;
}Ein Validierungsfehler gibt zurück:
{
"@type": "ConstraintViolationList",
"status": 422,
"violations": [
{
"propertyPath": "quantity",
"message": "Menge muss größer als null sein"
}
]
}Benutzerdefinierte Validatoren mit Klassen-Constraints behandeln feldübergreifende Validierung, beispielsweise die Sicherstellung, dass das Enddatum einer Bestellung nach dem Startdatum liegt.
Interview-Fragen: API Platform Tiefenwissen
Technische Interviews prüfen das Verständnis über die Grundnutzung hinaus. Diese Fragen erscheinen häufig bei Symfony-Backend-Positionen.
F: Wie unterscheidet sich API Platform vom manuellen Schreiben von Controllern?
API Platform generiert CRUD-Operationen aus Entity-Metadaten. Ein einzelnes #[ApiResource]-Attribut erzeugt GET-, POST-, PUT-, PATCH- und DELETE-Endpunkte mit OpenAPI-Dokumentation, Content Negotiation, Paginierung und Validierung. Manuelle Controller erfordern die separate Implementierung jeder Funktion. API Platform reduziert Boilerplate-Code um 70-80% für Standard-REST-Operationen und bleibt durch State Providers und Processors für benutzerdefinierte Logik erweiterbar.
F: Erklären Sie den Unterschied zwischen State Providers und State Processors.
State Providers behandeln den Datenabruf (GET-Operationen). Sie implementieren ProviderInterface::provide() und geben Entities, DTOs oder Collections zurück. State Processors behandeln Datenänderungen (POST, PUT, PATCH, DELETE). Sie implementieren ProcessorInterface::process() und empfangen das deserialisierte Objekt aus dem Request-Body. Diese Trennung folgt CQRS-Prinzipien: Lese- und Schreibvorgänge haben unterschiedliche Code-Pfade.
F: Wie verhindert man die Offenlegung sensibler Felder in API-Responses?
Serialisierungsgruppen steuern die Feldsichtbarkeit. Sensible Felder (Passwörter, interne IDs, Audit-Daten) werden schreibgeschützten Gruppen zugewiesen oder vollständig ausgeschlossen. #[Groups(['admin:read'])] für Felder verwenden, die nur Administratoren sehen sollten, dann den operations-spezifischen normalizationContext so konfigurieren, dass diese Gruppe nur bei Admin-Endpunkten enthalten ist.
F: Wie funktioniert die Paginierung in API Platform?
API Platform paginiert Collections standardmäßig mit 30 Elementen pro Seite. Der page-Query-Parameter steuert den Offset. Hydra-Metadaten in JSON-LD-Responses enthalten hydra:view mit first/last/next/previous-Links. Konfiguration über paginationItemsPerPage, paginationMaximumItemsPerPage und paginationClientItemsPerPage (erlaubt Clients, verschiedene Seitengrößen anzufordern).
F: Wann würde man ein DTO statt der direkten Entity-Exposition verwenden?
DTOs entkoppeln den API-Vertrag vom Datenbankschema. DTOs verwenden, wenn: (1) die API-Darstellung sich wesentlich von der Entity-Struktur unterscheidet, (2) mehrere Entities zu einer Response kombiniert werden, (3) berechnete Felder in Responses erscheinen, (4) die Eingabevalidierung von Entity-Constraints abweicht, oder (5) die Entity Doctrine Inheritance verwendet, die die Serialisierung kompliziert. DTOs verhindern auch die versehentliche Offenlegung neuer Entity-Felder bei Schemaänderungen.
API Platform Endpunkte testen
API Platform arbeitet mit Symfonys Test-Framework. ApiTestCase bietet Methoden für authentifizierte Anfragen und JSON-Response-Assertions.
namespace App\Tests\Api;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Product;
class ProductTest extends ApiTestCase
{
public function testGetCollection(): void
{
$response = static::createClient()->request('GET', '/api/products');
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains(['@context' => '/api/contexts/Product']);
$this->assertMatchesResourceCollectionJsonSchema(Product::class);
}
public function testCreateProduct(): void
{
$response = static::createClient()->request('POST', '/api/products', [
'json' => [
'name' => 'Test Product',
'price' => '29.99'
],
'headers' => [
'Authorization' => 'Bearer ' . $this->getAdminToken()
]
]);
$this->assertResponseStatusCodeSame(201);
$this->assertJsonContains([
'name' => 'Test Product',
'price' => '29.99'
]);
}
public function testCreateProductValidationFails(): void
{
static::createClient()->request('POST', '/api/products', [
'json' => ['name' => 'AB'], // Zu kurz
'headers' => ['Authorization' => 'Bearer ' . $this->getAdminToken()]
]);
$this->assertResponseStatusCodeSame(422);
$this->assertJsonContains([
'violations' => [
['propertyPath' => 'name']
]
]);
}
}Die Methode assertMatchesResourceCollectionJsonSchema() validiert Responses gegen das automatisch generierte JSON Schema und erkennt Regressionen bei Änderungen der Entity-Struktur.
Performance-Optimierung mit Eager Loading
N+1-Queries verschlechtern die Performance von Collection-Endpunkten. API Platforms fetchEager-Option und Doctrines Query Hints lösen dieses Problem.
#[ApiResource(
operations: [
new GetCollection(
extraProperties: ['doctrine_orm_fetch_join' => true]
)
]
)]
#[ORM\Entity]
class Order
{
#[ORM\ManyToOne(fetch: 'EAGER')]
#[ORM\JoinColumn(nullable: false)]
private Customer $customer;
#[ORM\OneToMany(mappedBy: 'order', fetch: 'EAGER')]
private Collection $items;
}Für komplexe Abfragen übertreffen benutzerdefinierte State Providers mit optimiertem DQL das automatische Eager Loading. Die Anzahl der Queries mit dem Symfony Profiler vor und nach der Optimierung messen.
Produktionsreife API Platform Konfiguration
# config/packages/api_platform.yaml
api_platform:
title: '%env(API_TITLE)%'
version: '%env(API_VERSION)%'
show_webby: false
defaults:
stateless: true
cache_headers:
max_age: 3600
shared_max_age: 3600
vary: ['Content-Type', 'Authorization']
extra_properties:
standard_put: true
exception_to_status:
Symfony\Component\Security\Core\Exception\AccessDeniedException: 403
App\Exception\BusinessException: 400show_webby (das Maskottchen) in Produktion deaktivieren. Die standard_put-Eigenschaft stellt sicher, dass PUT Ressourcen vollständig ersetzt anstatt zu mergen, entsprechend der RFC 7231-Semantik.
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Wichtige Erkenntnisse zu API Platform 2026
- State Providers rufen Daten für GET-Anfragen ab. State Processors verarbeiten POST/PUT/PATCH/DELETE. Diese Trennung ermöglicht sauberes Testen und benutzerdefinierte Datenquellen.
- Serialisierungsgruppen steuern, welche Felder in Responses erscheinen. Unterschiedliche Gruppen für Listen- vs. Detailansichten verwenden und schreibgeschützte Gruppen für Passwörter.
- Filter fügen Query-Parameter für Suche, Sortierung und Datumsbereiche hinzu. Integrierte Filter decken die meisten Fälle ab, benutzerdefinierte Filter behandeln komplexe Abfragen.
- DTOs entkoppeln API-Verträge von Datenbankschemas. Sie verwenden, wenn die Response-Struktur von der Entity abweicht oder Daten aus mehreren Quellen kombiniert werden.
- Der Symfony Serializer übernimmt die JSON-zu-Entity-Konvertierung. Das Verständnis seiner Normalizer und Context-Optionen ist für fortgeschrittene API Platform-Nutzung unerlässlich.
- API Platform 4.3 ist die aktuelle stabile Version. Version 5.0 (in Alpha) erfordert Symfony 7.4 oder 8.0 und beendet die Unterstützung älterer Symfony-Versionen.
- Dokumentation unter api-platform.com behandelt Randfälle, die hier nicht angesprochen werden. Die Doctrine ORM Integration erklärt die Entity-Beziehungsbehandlung im Detail.
Findest du den Bug in Symfony?
Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Geschrieben von
Anthony Fillion-MailletGründer von SharpSkill
Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.
Aktualisiert am 25. August 2026
Tags
Teilen
Verwandte Artikel

Doctrine ORM: Beziehungen in Symfony meistern
Vollständiger Leitfaden zu Doctrine-ORM-Beziehungen in Symfony. OneToMany, ManyToMany, Ladestrategien und Performance-Optimierung mit praktischen Beispielen.

Symfony-Interviewfragen: Top 25 in 2026
Die 25 häufigsten Symfony-Interviewfragen. Architektur, Doctrine ORM, Services, Security, Formulare und Tests mit ausführlichen Antworten und Codebeispielen.

Symfony 7: API Platform und Best Practices
Vollstaendiger Leitfaden zu API Platform 4 mit Symfony 7. State Processors, State Providers, Serialisierungsgruppen und erweiterte Validierung fuer professionelle REST-APIs.