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 Symfony REST Tutorial 2026

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.

Architektura API Platform 4

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.

bash
# Install API Platform with Symfony Flex
composer require api

# Verify installation
php bin/console debug:router | grep api

Recept api instaluje api-platform/symfony wraz z komponentami serializer, validator i property-access. Symfony Flex automatycznie konfiguruje trasy pod /api.

yaml
# 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].

src/Entity/Product.phpphp
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.

src/Entity/User.phpphp
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.

src/State/ProductStatsProvider.phpphp
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
        );
    }
}
src/Dto/ProductStats.phpphp
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.

src/State/UserRegistrationProcessor.phpphp
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:

php
#[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.

src/Entity/Article.phpphp
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.

src/Entity/Order.phpphp
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:

json
{
    "@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.

tests/Api/ProductTest.phpphp
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.

php
#[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ę

yaml
# 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: 400

Należ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.
Wyzwanie dnia

Znajdziesz błąd w Symfony?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 25 sierpnia 2026

Tagi

#api-platform
#symfony
#rest-api
#php
#tutorial

Udostępnij

Powiązane artykuły