API Platform Symfony REST: Повний посібник та питання на співбесіді 2026
Створення продакшн-готових REST API з API Platform 4 та Symfony 7. State Providers, Processors, фільтри та найпоширеніші питання на співбесіді про API Platform.

API Platform 4.3 перетворює Symfony на потужну платформу для REST API з автоматичною документацією OpenAPI, узгодженням контенту та чистою архітектурою, що розділяє операції читання та запису. Цей посібник охоплює налаштування, просунуті патерни та питання на співбесіді, які відрізняють сеньйорів від джуніорів.
API Platform 4 використовує State Providers для операцій GET та State Processors для POST/PUT/PATCH/DELETE. Такий поділ відповідає принципам CQRS і спрощує тестування.
Встановлення API Platform на Symfony 7
Встановлення API Platform вимагає Symfony 7.2 або вище. Bundle інтегрується з Doctrine ORM за замовчуванням, але підтримує користувацькі джерела даних через патерн Provider/Processor.
# Install API Platform with Symfony Flex
composer require api
# Verify installation
php bin/console debug:router | grep apiРецепт api встановлює api-platform/symfony разом з компонентами serializer, validator та property-access. Symfony Flex автоматично налаштовує маршрути під /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']Налаштування stateless: true вимикає PHP-сесії для всіх API-ендпоінтів, зменшуючи використання пам'яті та уможливлюючи горизонтальне масштабування.
Створення першого API-ресурсу з атрибутами
API Platform 4 використовує атрибути PHP 8 для оголошення API-ресурсів. Кожна сутність стає API-ендпоінтом через атрибут #[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;
}
}Параметр security у кожній операції використовує мову виразів Symfony. Функція is_granted() перевіряє рішення voter-ів, забезпечуючи контроль доступу на основі ролей без користувацьких контролерів.
Групи серіалізації для формування відповідей
Групи серіалізації контролюють, які властивості з'являються у відповідях API. Різні операції можуть показувати різні поля з тієї самої сутності.
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 контролює вихід (PHP в JSON). denormalizationContext контролює вхід (JSON в PHP). Поле password використовує лише user:write, запобігаючи його появі у відповідях.
State Providers для користувацьких джерел даних
State Providers отримують дані для операцій GET. Стандартні ItemProvider та CollectionProvider використовують Doctrine, але користувацькі провайдери дозволяють будь-яке джерело даних: Elasticsearch, зовнішні API або обчислювані значення.
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 не є сутністю Doctrine. Він представляє обчислені дані, доступні за адресою /api/products/stats. Провайдер обчислює значення при кожному запиті.
Готовий до співбесід з Symfony?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
State Processors для операцій запису
State Processors обробляють операції POST, PUT, PATCH та DELETE. Користувацькі процесори дозволяють виконувати бізнес-логіку до або після збереження.
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;
}
}Процесор прикріплюється до операції POST на сутності User:
#[ApiResource(
operations: [
new Post(
processor: UserRegistrationProcessor::class,
denormalizationContext: ['groups' => ['user:create']]
)
]
)]Процесор хешує пароль перед тим, як Doctrine збереже сутність. Властивість plainPassword використовує групу серіалізації лише для запису і ніколи не зберігається в базі даних.
Фільтри для параметрів запитів
API Platform надає вбудовані фільтри для пошуку, сортування та фільтрації колекцій. Фільтри додають параметри запиту до GET-ендпоінтів колекцій.
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...
}Конфігурація фільтрів увімкнює такі параметри запитів:
GET /api/articles?title=symfony(часткове співпадіння заголовка)GET /api/articles?author.name=John(точне співпадіння пов'язаної сутності)GET /api/articles?publishedAt[after]=2026-01-01(діапазон дат)GET /api/articles?order[publishedAt]=desc(сортування)
Користувацькі фільтри розширюють AbstractFilter для складної логіки запитів, яку вбудовані фільтри не можуть виразити.
Обробка помилок та валідація
API Platform інтегрується з Symfony Validator. Помилки валідації повертають 422 Unprocessable Entity з деталями проблеми 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;
}Помилка валідації повертає:
{
"@type": "ConstraintViolationList",
"status": 422,
"violations": [
{
"propertyPath": "quantity",
"message": "Quantity must be greater than zero"
}
]
}Користувацькі валідатори з обмеженнями на рівні класу обробляють крос-польову валідацію, наприклад, перевірку того, що дата закінчення замовлення пізніше дати початку.
Питання на співбесіді: глибокі знання API Platform
Технічні співбесіди досліджують розуміння за межами базового використання. Ці питання часто з'являються для позицій Symfony backend.
П: Чим API Platform відрізняється від написання контролерів вручну?
API Platform генерує CRUD-операції з метаданих сутності. Один атрибут #[ApiResource] створює ендпоінти GET, POST, PUT, PATCH та DELETE з документацією OpenAPI, узгодженням контенту, пагінацією та валідацією. Ручні контролери вимагають окремої реалізації кожної функції. API Platform зменшує boilerplate на 70-80% для стандартних REST-операцій, залишаючись розширюваним для користувацької логіки через State Providers та Processors.
П: Поясніть різницю між State Providers та State Processors.
State Providers обробляють отримання даних (операції GET). Вони реалізують ProviderInterface::provide() та повертають сутності, DTO або колекції. State Processors обробляють мутацію даних (POST, PUT, PATCH, DELETE). Вони реалізують ProcessorInterface::process() та отримують десеріалізований об'єкт з тіла запиту. Такий поділ відповідає принципам CQRS: читання та запис мають окремі шляхи коду.
П: Як запобігти відображенню чутливих полів у відповідях API?
Групи серіалізації контролюють видимість полів. Призначте чутливі поля (паролі, внутрішні ID, дані аудиту) групам лише для запису або повністю виключіть їх. Використовуйте #[Groups(['admin:read'])] для полів, які повинні бачити лише адміністратори, потім налаштуйте normalizationContext на рівні операції, щоб включити цю групу лише для адміністративних ендпоінтів.
П: Як працює пагінація в API Platform?
API Platform пагінує колекції за замовчуванням по 30 елементів на сторінку. Параметр page контролює зміщення. Метадані Hydra у відповідях JSON-LD включають hydra:view з посиланнями first/last/next/previous. Налаштовується через paginationItemsPerPage, paginationMaximumItemsPerPage та paginationClientItemsPerPage (дозволяє клієнтам запитувати різні розміри сторінок).
П: Коли використовувати DTO замість безпосереднього відображення сутності?
DTO відокремлюють контракт API від схеми бази даних. Використовуйте DTO коли: (1) представлення API значно відрізняється від структури сутності, (2) кілька сутностей об'єднуються в одну відповідь, (3) обчислювані поля з'являються у відповідях, (4) валідація вводу відрізняється від обмежень сутності, або (5) сутність використовує успадкування Doctrine, що ускладнює серіалізацію. DTO також запобігають випадковому відображенню нових полів сутності при зміні схеми.
Практикуйте ці питання з задачами API Platform для співбесіди, щоб закріпити розуміння.
Тестування ендпоінтів API Platform
API Platform працює з фреймворком тестування Symfony. ApiTestCase надає методи для виконання автентифікованих запитів та перевірки 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']
]
]);
}
}Метод assertMatchesResourceCollectionJsonSchema() валідує відповіді проти автоматично згенерованої JSON Schema, виявляючи регресії при зміні структури сутності.
Оптимізація продуктивності з Eager Loading
Запити N+1 погіршують продуктивність ендпоінтів колекцій. Опція fetchEager API Platform та query hints Doctrine вирішують цю проблему.
#[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;
}Для складних запитів користувацькі State Providers з оптимізованим DQL перевершують автоматичний eager loading. Вимірюйте кількість запитів за допомогою Symfony Profiler до та після оптимізації.
Конфігурація API Platform для продакшену
# 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Вимкніть show_webby (маскот) у продакшені. Властивість standard_put гарантує, що PUT повністю замінює ресурси, а не об'єднує їх, дотримуючись семантики RFC 7231.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Що варто пам'ятати про API Platform у 2026 році
- State Providers отримують дані для GET-запитів. State Processors обробляють POST/PUT/PATCH/DELETE. Такий поділ забезпечує чисте тестування та користувацькі джерела даних.
- Групи серіалізації контролюють, які поля з'являються у відповідях. Використовуйте різні групи для перегляду списків та деталей, а також групи лише для запису для паролів.
- Фільтри додають параметри запитів для пошуку, сортування та діапазонів дат. Вбудовані фільтри покривають більшість випадків, а користувацькі фільтри обробляють складні запити.
- DTO відокремлюють контракти API від схем бази даних. Використовуйте їх, коли структура відповіді відрізняється від сутності або коли дані поєднуються з кількох джерел.
- Symfony Serializer обробляє перетворення JSON в сутність. Розуміння його нормалайзерів та опцій контексту є essential для просунутого використання API Platform.
- API Platform 4.3 — поточна стабільна версія. Версія 5.0 (в альфа-стадії) вимагає Symfony 7.4 або 8.0 та припиняє підтримку старіших версій Symfony.
- Документація на api-platform.com охоплює edge cases, не розглянуті тут. Документація інтеграції Doctrine ORM детально пояснює обробку зв'язків сутностей.
Чи знайдеш ти помилку в Symfony?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Автор:
Anthony Fillion-MailletЗасновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 25 серпня 2026 р.
Теги
Поділитися
Пов'язані статті

API Platform із Symfony у 2026: Архітектура, State Providers та питання на співбесіді
Опануйте API Platform 4.2 із Symfony: State Providers, Processors, Object Mapper, JSON Streamer та оптимізації продуктивності. Найпоширеніші питання на співбесіді для досвідчених розробників.

API Platform GraphQL Symfony: Схеми, Мутації та Питання на Співбесіді 2026
Повний посібник з інтеграції GraphQL з API Platform у Symfony. Схеми, запити, мутації, резолвери, безпека та питання на технічну співбесіду.

Symfony 8 у 2026 році: нові можливості, PHP 8.4 Lazy Objects та питання для співбесід
Symfony 8 new features: нативні lazy objects PHP 8.4, багатокрокові форми, invokable-команди, JSON Streamer та питання для технічних співбесід 2026.