API Platform Symfony REST: Kompletny Poradnik i Pytania Rekrutacyjne 2026
Tworzenie produkcyjnych API REST z API Platform 4 i Symfony 7. State Providers, Processors, filtry oraz najczęstsze pytania rekrutacyjne dotyczące API Platform.

API Platform 4.3 przekształca Symfony w potężne narzędzie do budowy REST API z automatyczną dokumentacją OpenAPI, negocjacją treści oraz czystą architekturą separującą operacje odczytu i zapisu. Ten poradnik obejmuje konfigurację, zaawansowane wzorce oraz pytania rekrutacyjne, które odróżniają seniorów od juniorów.
API Platform 4 wykorzystuje State Providers do operacji GET oraz State Processors do POST/PUT/PATCH/DELETE. Ta separacja jest zgodna z zasadami CQRS i ułatwia testowanie.
Instalacja API Platform na Symfony 7
Instalacja API Platform wymaga Symfony 7.2 lub nowszego. Bundle integruje się domyślnie z Doctrine ORM, ale obsługuje niestandardowe źródła danych poprzez wzorzec Provider/Processor.
# Install API Platform with Symfony Flex
composer require api
# Verify installation
php bin/console debug:router | grep apiRecept api instaluje api-platform/symfony wraz z komponentami serializer, validator i property-access. Symfony Flex automatycznie konfiguruje trasy pod /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']Ustawienie stateless: true wyłącza sesje PHP dla wszystkich endpointów API, redukując zużycie pamięci i umożliwiając skalowanie horyzontalne.
Tworzenie Pierwszego Zasobu API z Atrybutami
API Platform 4 wykorzystuje atrybuty PHP 8 do deklarowania zasobów API. Każda encja staje się endpointem API poprzez atrybut #[ApiResource].
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;
}
}Parametr security w każdej operacji wykorzystuje język wyrażeń Symfony. Funkcja is_granted() sprawdza decyzje voterów, umożliwiając kontrolę dostępu opartą na rolach bez niestandardowych kontrolerów.
Grupy Serializacji do Kształtowania Odpowiedzi
Grupy serializacji kontrolują, które właściwości pojawiają się w odpowiedziach API. Różne operacje mogą eksponować różne pola z tej samej encji.
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 = [];
}normalizationContext kontroluje wyjście (PHP do JSON). denormalizationContext kontroluje wejście (JSON do PHP). Pole password używa tylko user:write, co zapobiega jego pojawianiu się w odpowiedziach.
State Providers dla Niestandardowych Źródeł Danych
State Providers pobierają dane dla operacji GET. Domyślne ItemProvider i CollectionProvider używają Doctrine, ale niestandardowe providery umożliwiają wykorzystanie dowolnego źródła danych: Elasticsearch, zewnętrznych API lub wartości obliczanych.
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
) {}
}DTO ProductStats nie jest encją Doctrine. Reprezentuje obliczone dane dostępne pod /api/products/stats. Provider oblicza wartości przy każdym żądaniu.
Gotowy na rozmowy o Symfony?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
State Processors dla Operacji Zapisu
State Processors obsługują operacje POST, PUT, PATCH i DELETE. Niestandardowe procesory umożliwiają wykonywanie logiki biznesowej przed lub po zapisie.
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 is the deserialized User entity from the request body
$hashedPassword = $this->passwordHasher->hashPassword(
$data,
$data->getPlainPassword()
);
$data->setPassword($hashedPassword);
$data->eraseCredentials();
$this->entityManager->persist($data);
$this->entityManager->flush();
return $data;
}
}Procesor należy dołączyć do operacji POST na encji User:
#[ApiResource(
operations: [
new Post(
processor: UserRegistrationProcessor::class,
denormalizationContext: ['groups' => ['user:create']]
)
]
)]Procesor hashuje hasło przed zapisaniem encji przez Doctrine. Właściwość plainPassword używa grupy serializacji tylko do zapisu i nigdy nie jest przechowywana w bazie danych.
Filtry dla Parametrów Zapytań
API Platform dostarcza wbudowane filtry do wyszukiwania, sortowania i filtrowania kolekcji. Filtry dodają parametry zapytań do endpointów GET kolekcji.
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 properties...
}Konfiguracja filtrów umożliwia następujące parametry zapytań:
GET /api/articles?title=symfony(częściowe dopasowanie tytułu)GET /api/articles?author.name=John(dokładne dopasowanie powiązanej encji)GET /api/articles?publishedAt[after]=2026-01-01(zakres dat)GET /api/articles?order[publishedAt]=desc(sortowanie)
Niestandardowe filtry rozszerzają AbstractFilter dla złożonej logiki zapytań, której wbudowane filtry nie obsługują.
Obsługa Błędów i Walidacja
API Platform integruje się z Symfony Validator. Błędy walidacji zwracają 422 Unprocessable Entity ze szczegółami problemu RFC 7807.
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ApiResource]
class Order
{
#[ORM\Column]
#[Assert\NotBlank(message: 'Order quantity is required')]
#[Assert\Positive(message: 'Quantity must be greater than zero')]
#[Assert\LessThanOrEqual(value: 100, message: 'Maximum 100 items per order')]
private int $quantity;
}Błąd walidacji zwraca:
{
"@type": "ConstraintViolationList",
"status": 422,
"violations": [
{
"propertyPath": "quantity",
"message": "Quantity must be greater than zero"
}
]
}Niestandardowe walidatory z ograniczeniami na poziomie klasy obsługują walidację między polami, np. zapewniając, że data końcowa zamówienia następuje po dacie początkowej.
Pytania Rekrutacyjne: Dogłębna Znajomość API Platform
Rozmowy techniczne badają zrozumienie wykraczające poza podstawowe użycie. Te pytania pojawiają się często na stanowiskach backendowych Symfony.
P: Czym API Platform różni się od pisania kontrolerów ręcznie?
API Platform generuje operacje CRUD z metadanych encji. Pojedynczy atrybut #[ApiResource] tworzy endpointy GET, POST, PUT, PATCH i DELETE z dokumentacją OpenAPI, negocjacją treści, paginacją i walidacją. Ręczne kontrolery wymagają osobnej implementacji każdej funkcji. API Platform redukuje boilerplate o 70-80% dla standardowych operacji REST, pozostając rozszerzalne dla niestandardowej logiki poprzez State Providers i Processors.
P: Wyjaśnij różnicę między State Providers a State Processors.
State Providers obsługują pobieranie danych (operacje GET). Implementują ProviderInterface::provide() i zwracają encje, DTO lub kolekcje. State Processors obsługują mutację danych (POST, PUT, PATCH, DELETE). Implementują ProcessorInterface::process() i otrzymują zdeserializowany obiekt z ciała żądania. Ta separacja jest zgodna z zasadami CQRS: odczyty i zapisy mają odrębne ścieżki kodu.
P: Jak zapobiec eksponowaniu wrażliwych pól w odpowiedziach API?
Grupy serializacji kontrolują widoczność pól. Wrażliwe pola (hasła, wewnętrzne ID, dane audytu) należy przypisać do grup tylko do zapisu lub całkowicie je wykluczyć. Użycie #[Groups(['admin:read'])] dla pól, które powinni widzieć tylko administratorzy, a następnie konfiguracja normalizationContext na poziomie operacji, aby uwzględnić tę grupę tylko dla endpointów administracyjnych.
P: Jak działa paginacja w API Platform?
API Platform domyślnie paginuje kolekcje po 30 elementów na stronę. Parametr page kontroluje offset. Metadane Hydra w odpowiedziach JSON-LD zawierają hydra:view z linkami first/last/next/previous. Konfiguracja poprzez paginationItemsPerPage, paginationMaximumItemsPerPage i paginationClientItemsPerPage (pozwala klientom żądać różnych rozmiarów stron).
P: Kiedy użyć DTO zamiast bezpośredniego eksponowania encji?
DTO oddzielają kontrakt API od schematu bazy danych. DTO należy używać gdy: (1) reprezentacja API znacząco różni się od struktury encji, (2) wiele encji łączy się w jedną odpowiedź, (3) pola obliczane pojawiają się w odpowiedziach, (4) walidacja wejścia różni się od ograniczeń encji, lub (5) encja używa dziedziczenia Doctrine, które komplikuje serializację. DTO zapobiegają również przypadkowemu eksponowaniu nowych pól encji przy zmianach schematu.
Praktyka tych pytań z wyzwaniami rekrutacyjnymi API Platform wzmocni zrozumienie.
Testowanie Endpointów API Platform
API Platform współpracuje z frameworkiem testowym Symfony. ApiTestCase dostarcza metody do wykonywania uwierzytelnionych żądań i asercji odpowiedzi JSON.
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'], // Too short
'headers' => ['Authorization' => 'Bearer ' . $this->getAdminToken()]
]);
$this->assertResponseStatusCodeSame(422);
$this->assertJsonContains([
'violations' => [
['propertyPath' => 'name']
]
]);
}
}Metoda assertMatchesResourceCollectionJsonSchema() waliduje odpowiedzi względem automatycznie generowanego JSON Schema, wykrywając regresje przy zmianach struktury encji.
Optymalizacja Wydajności z Eager Loading
Zapytania N+1 degradują wydajność endpointów kolekcji. Opcja fetchEager API Platform i query hints Doctrine rozwiązują ten 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;
}Dla złożonych zapytań niestandardowe State Providers z zoptymalizowanym DQL przewyższają automatyczny eager loading. Należy mierzyć liczbę zapytań za pomocą Symfony Profiler przed i po optymalizacji.
Konfiguracja API Platform Gotowa na Produkcję
# 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: 400Należy wyłączyć show_webby (maskotkę) w produkcji. Właściwość standard_put zapewnia, że PUT całkowicie zastępuje zasoby zamiast je łączyć, zgodnie z semantyką RFC 7231.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Co Warto Zapamiętać o API Platform w 2026
- State Providers pobierają dane dla żądań GET. State Processors obsługują POST/PUT/PATCH/DELETE. Ta separacja umożliwia czyste testowanie i niestandardowe źródła danych.
- Grupy serializacji kontrolują, które pola pojawiają się w odpowiedziach. Należy używać różnych grup dla widoków listy i szczegółów oraz grup tylko do zapisu dla haseł.
- Filtry dodają parametry zapytań do wyszukiwania, sortowania i zakresów dat. Wbudowane filtry obsługują większość przypadków, a niestandardowe filtry radzą sobie ze złożonymi zapytaniami.
- DTO oddzielają kontrakty API od schematów bazy danych. Należy ich używać, gdy struktura odpowiedzi różni się od encji lub gdy dane pochodzą z wielu źródeł.
- Symfony Serializer obsługuje konwersję JSON na encję. Zrozumienie jego normalizerów i opcji kontekstu jest niezbędne dla zaawansowanego użycia API Platform.
- API Platform 4.3 to aktualna stabilna wersja. Wersja 5.0 (w wersji alfa) wymaga Symfony 7.4 lub 8.0 i rezygnuje ze wsparcia dla starszych wersji Symfony.
- Dokumentacja na api-platform.com obejmuje przypadki brzegowe nieomówione tutaj. Dokumentacja integracji Doctrine ORM szczegółowo wyjaśnia obsługę relacji encji.
Znajdziesz błąd w Symfony?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 25 sierpnia 2026
Tagi
Udostępnij
Powiązane artykuły

API Platform z Symfony w 2026: Architektura, State Providers i pytania rekrutacyjne
Opanuj API Platform 4.2 z Symfony: State Providers, Processors, Object Mapper, JSON Streamer i optymalizacje wydajności. Najczęstsze pytania rekrutacyjne dla doświadczonych programistów.

API Platform GraphQL w Symfony: Schematy, Mutacje i Pytania Rekrutacyjne 2026
Kompletny przewodnik po integracji GraphQL z API Platform w Symfony. Schematy, zapytania, mutacje, resolwery, zabezpieczenia i pytania na rozmowę kwalifikacyjną.

Symfony 8 w 2026 roku: nowe funkcje, PHP 8.4 Lazy Objects i pytania rekrutacyjne
Symfony 8 wprowadza natywne lazy objects PHP 8.4, formularze wielokrokowe, komendy invokable i nowe komponenty. Poznaj kluczowe zmiany i pytania rekrutacyjne.