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 4.2 zmienia sposób, w jaki aplikacje Symfony udostępniają interfejsy REST i GraphQL. Ta wersja wprowadza Symfony Object Mapper do czystego rozdzielenia zasobów, JSON Streamer dla znaczącego wzrostu wydajności oraz przeprojektowany system filtrów. Dla programistów przygotowujących się do rozmów kwalifikacyjnych, zrozumienie tych wzorców architektonicznych wyróżnia seniorów od juniorów.
API Platform 4.2 wymaga Symfony 7.4 lub 8.0. Wsparcie dla Symfony 6.4 i 7.0-7.3 zostało wycofane. JSON Streamer zapewnia do 32% więcej żądań na sekundę na endpointach kolekcji.
Konfiguracja API Platform 4.2 z Symfony
API Platform instaluje się przez Symfony Flex z automatyczną konfiguracją. Domyślna konfiguracja obsługuje większość przypadków użycia, pozostając jednocześnie w pełni konfigurowalną dla złożonych wymagań domenowych.
# Instalacja API Platform
composer require api-platform/symfony
# Dokumentacja API dostępna pod /api/
# Otwórz http://localhost:8000/api/ po uruchomieniu serwera
symfony serveRecipe Flex konfiguruje grupy serializacji, integrację z Doctrine oraz generowanie dokumentacji OpenAPI. Zasoby API udostępniają operacje CRUD poprzez dodanie pojedynczego atrybutu do klas encji.
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')")
],
normalizationContext: ['groups' => ['book:read']],
denormalizationContext: ['groups' => ['book:write']]
)]
class Book
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Groups(['book:read', 'book:write'])]
private string $title;
#[ORM\Column(type: 'text')]
#[Groups(['book:read', 'book:write'])]
private string $description;
#[ORM\Column]
#[Groups(['book:read'])]
private \DateTimeImmutable $createdAt;
// Gettery i settery...
}Ta konfiguracja generuje pięć endpointów z automatyczną walidacją, serializacją i dokumentacją OpenAPI.
State Providers: Pobieranie danych z dowolnego źródła
State Providers kontrolują sposób, w jaki API Platform pobiera dane dla operacji GET. Domyślny provider Doctrine obsługuje pobieranie encji, ale własne providery umożliwiają integrację z zewnętrznymi API, Elasticsearch lub danymi z pamięci podręcznej.
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Repository\BookRepository;
use Psr\Cache\CacheItemPoolInterface;
final class BookStateProvider implements ProviderInterface
{
public function __construct(
private BookRepository $repository,
private CacheItemPoolInterface $cache
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
// Pobieranie pojedynczego elementu
if (isset($uriVariables['id'])) {
$cacheKey = sprintf('book_%d', $uriVariables['id']);
$item = $this->cache->getItem($cacheKey);
if ($item->isHit()) {
return $item->get();
}
$book = $this->repository->find($uriVariables['id']);
$item->set($book)->expiresAfter(3600);
$this->cache->save($item);
return $book;
}
// Pobieranie kolekcji z własnym filtrowaniem
return $this->repository->findActiveBooks();
}
}Rejestracja providera dla konkretnych operacji:
#[ApiResource(
operations: [
new GetCollection(provider: BookStateProvider::class),
new Get(provider: BookStateProvider::class),
// Pozostałe operacje używają domyślnego providera Doctrine
new Post(),
new Put(),
]
)]
class Book { /* ... */ }State Processors: Obsługa mutacji z logiką biznesową
State Processors obsługują operacje POST, PUT, PATCH i DELETE. Otrzymują zdeserializowane dane i aplikują logikę biznesową przed zapisem.
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Book;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
final class BookStateProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private MailerInterface $mailer,
private ProcessorInterface $persistProcessor
) {}
public function process(
mixed $data,
Operation $operation,
array $uriVariables = [],
array $context = []
): mixed {
// Logika biznesowa przed zapisem
if ($data instanceof Book && $operation instanceof Post) {
$data->setCreatedAt(new \DateTimeImmutable());
$data->setSlug($this->generateSlug($data->getTitle()));
}
// Delegowanie do procesora Doctrine
$result = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
// Powiadomienie po zapisie
if ($operation instanceof Post) {
$this->notifyNewBook($data);
}
return $result;
}
private function generateSlug(string $title): string
{
return strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', $title));
}
private function notifyNewBook(Book $book): void
{
$email = (new Email())
->to('catalog@example.com')
->subject('Nowa książka dodana: ' . $book->getTitle())
->text('Nowa książka została dodana do katalogu.');
$this->mailer->send($email);
}
}Dekorowanie domyślnego procesora Doctrine za pomocą #[AsDecorator] zachowuje zachowanie persystencji, dodając jednocześnie własną logikę. Ten wzorzec eliminuje konieczność duplikowania operacji ORM.
Object Mapper: Rozdzielenie zasobów API od encji
API Platform 4.2 integruje komponent Symfony Object Mapper w celu oddzielenia reprezentacji API od encji domenowych. Ta separacja umożliwia różne modele odczytu/zapisu i chroni wewnętrzne struktury encji.
namespace App\ApiResource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use App\Entity\Book;
use Symfony\Component\ObjectMapper\Attribute\Map;
#[ApiResource(
shortName: 'Book',
operations: [
new GetCollection(),
new Get()
]
)]
#[Map(target: Book::class)]
class BookResource
{
public ?int $id = null;
public string $title;
public string $description;
// Pole obliczane, nie w encji
public int $wordCount;
// Sformatowana data dla konsumentów API
public string $publishedDate;
}Provider mappera automatycznie transformuje encje na zasoby:
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\BookResource;
use App\Repository\BookRepository;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
final class BookResourceProvider implements ProviderInterface
{
public function __construct(
private BookRepository $repository,
private ObjectMapperInterface $mapper
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (isset($uriVariables['id'])) {
$book = $this->repository->find($uriVariables['id']);
return $book ? $this->toResource($book) : null;
}
return array_map(
fn(Book $book) => $this->toResource($book),
$this->repository->findAll()
);
}
private function toResource(Book $book): BookResource
{
$resource = $this->mapper->map($book, BookResource::class);
$resource->wordCount = str_word_count($book->getDescription());
$resource->publishedDate = $book->getCreatedAt()->format('j F Y');
return $resource;
}
}Gotowy na rozmowy o Symfony?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
JSON Streamer: 32% wzrost wydajności
Komponent JSON Streamer serializuje duże kolekcje bez ładowania całych zbiorów danych do pamięci. Benchmarki na API Sylius wykazały 32,4% wzrost liczby żądań na sekundę.
Włączenie streamingu na poziomie zasobu lub operacji:
#[ApiResource(
operations: [
new GetCollection(
jsonStream: true, // Włączenie JSON streaming
paginationItemsPerPage: 100
),
new Get()
]
)]
class Book { /* ... */ }Streaming jest szczególnie korzystny dla:
- Endpointów kolekcji z ponad 50 elementami
- Zasobów z zagnieżdżonymi relacjami
- API obsługujących klientów mobilnych z ograniczoną przepustowością
Specyfikacja OpenAPI również została zoptymalizowana. Mutualizacja JSON Schema zmniejsza rozmiar pliku o 30%, poprawiając czasy ładowania dokumentacji.
Pytania rekrutacyjne: Architektura API Platform
Rozmowy techniczne na stanowiska Symfony często obejmują wzorce API Platform. Te pytania oceniają zrozumienie architektury frameworka wykraczające poza podstawowe operacje CRUD.
"Wyjaśnij różnicę między State Providers a Processors"
Oczekiwana odpowiedź: State Providers pobierają dane dla operacji odczytu (GET). Zwracają encje, DTO lub tablice. State Processors obsługują operacje zapisu (POST, PUT, PATCH, DELETE). Otrzymują zdeserializowane dane wejściowe i wykonują logikę biznesową przed zapisem. Rozdzielenie to podąża za zasadami CQRS: zapytania przez Providers, komendy przez Processors.
"Kiedy użyć własnego API Resource zamiast bezpośredniego udostępniania encji?"
Oczekiwana odpowiedź: Własne zasoby stosuje się gdy:
- Reprezentacja API różni się od schematu bazy danych
- Pola obliczane wymagają agregacji z wielu encji
- Modele zapisu i odczytu wymagają różnych struktur
- Wewnętrzne pola encji muszą pozostać ukryte przed konsumentami API
- Kompatybilność wersji wymaga stabilnych kontraktów podczas ewolucji encji
"Jak API Platform obsługuje walidację?"
Oczekiwana odpowiedź: API Platform używa constraintów Symfony Validator na właściwościach encji. Walidacja uruchamia się automatycznie podczas deserializacji przed wykonaniem State Processor. Grupy walidacyjne kontrolują, które constrainty są stosowane dla danej operacji. Własne walidatory integrują się poprzez standardowe mechanizmy Symfony.
#[ApiResource(
operations: [
new Post(validationContext: ['groups' => ['create']]),
new Put(validationContext: ['groups' => ['update']])
]
)]
class Book
{
#[Assert\NotBlank(groups: ['create', 'update'])]
private string $title;
#[Assert\Isbn(groups: ['create'])]
private string $isbn; // Wymagane tylko przy tworzeniu
}Kandydaci często opisują walidację jako "automatyczną" bez wspominania o grupach walidacyjnych lub własnych constraintach. Rekruterzy szukają zrozumienia sposobu dostosowywania walidacji per operacja.
"Jakie mechanizmy bezpieczeństwa zapewnia API Platform?"
Oczekiwana odpowiedź: API Platform integruje się z Symfony Security poprzez:
- Atrybut
securityna operacjach dla dostępu opartego na rolach securityPostDenormalizedla sprawdzeń na poziomie obiektu po bindowaniu danych- Votery dla złożonej logiki autoryzacji
- Rate limiting przez integrację z Symfony Rate Limiter
#[ApiResource(
operations: [
new Get(
security: "is_granted('ROLE_USER')"
),
new Put(
security: "is_granted('ROLE_ADMIN') or object.getOwner() == user",
securityPostDenormalize: "is_granted('BOOK_EDIT', object)"
)
]
)]Filtry i paginacja: Zaawansowane wzorce zapytań
Filtry API Platform umożliwiają klientom wykonywanie zapytań do kolekcji za pomocą parametrów URL. System filtrów w wersji 4.2 został przeprojektowany dla lepszej rozszerzalności.
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Metadata\ApiFilter;
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: [
'title' => 'partial', // LIKE %value%
'author.name' => 'exact' // Zagnieżdżona właściwość
])]
#[ApiFilter(DateFilter::class, properties: ['createdAt'])]
#[ApiFilter(OrderFilter::class, properties: ['title', 'createdAt'])]
class Book { /* ... */ }Wygenerowane endpointy:
GET /api/books?title=symfony # Szukanie po tytule
GET /api/books?createdAt[after]=2026-01-01 # Zakres dat
GET /api/books?order[createdAt]=desc # SortowanieTestowanie zasobów API Platform
API Platform dostarcza klienta testowego upraszczającego testy funkcjonalne. Klasa ApiTestCase oferuje asercje specyficzne dla odpowiedzi API.
namespace App\Tests\Api;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;
class BookTest extends ApiTestCase
{
public function testGetCollection(): void
{
$response = static::createClient()->request('GET', '/api/books');
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/api/contexts/Book',
'@type' => 'Collection'
]);
}
public function testCreateBook(): void
{
$response = static::createClient()->request('POST', '/api/books', [
'json' => [
'title' => 'Najlepsze praktyki Symfony',
'description' => 'Przewodnik po nowoczesnym rozwoju Symfony'
],
'headers' => ['Authorization' => 'Bearer ' . $this->getToken()]
]);
$this->assertResponseStatusCodeSame(201);
$this->assertJsonContains(['title' => 'Najlepsze praktyki Symfony']);
}
public function testCreateBookValidationFails(): void
{
$response = static::createClient()->request('POST', '/api/books', [
'json' => ['description' => 'Brak tytułu'],
'headers' => ['Authorization' => 'Bearer ' . $this->getToken()]
]);
$this->assertResponseStatusCodeSame(422);
$this->assertJsonContains([
'violations' => [
['propertyPath' => 'title', 'message' => 'This value should not be blank.']
]
]);
}
}Kluczowe wnioski dla API Platform z Symfony
- State Providers obsługują operacje GET, State Processors obsługują mutacje. Ta separacja umożliwia czystą architekturę z własnymi źródłami danych i logiką biznesową
- Komponent Object Mapper oddziela zasoby API od encji Doctrine, umożliwiając różne modele odczytu/zapisu i chroniąc wewnętrzne struktury
- JSON Streamer zapewnia 32% wzrost wydajności na endpointach kolekcji poprzez serializację bez pełnej alokacji pamięci
- Bezpieczeństwo integruje się poprzez standardowe mechanizmy Symfony: wyrażenia
security, Votery i komponent Rate Limiter - Grupy walidacyjne dostosowują egzekwowanie constraintów per operacja
- Filtry automatycznie udostępniają parametry zapytań, z filtrami search, date i order pokrywającymi większość przypadków użycia
- Pytania rekrutacyjne koncentrują się na decyzjach architektonicznych: kiedy używać własnych providerów, jak rozdzielać modele odczytu/zapisu oraz wzorce implementacji bezpieczeństwa
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
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 8 września 2026
Tagi
Udostępnij
Powiązane artykuły

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.

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.

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ą.