Как реализовать API Platform в проекте Symfony?
API Platform превращает PHP-класс с атрибутом #[ApiResource] в полноценный REST/GraphQL API: маршруты, сериализацию, валидацию и OpenAPI-документацию фреймворк генерирует автоматически.
Что делает API Platform
API Platform — это слой поверх Symfony, который берёт Doctrine-сущность (или любой PHP-класс) и автоматически генерирует для неё набор HTTP-операций: GET /products, GET /products/{id}, POST /products, PUT, PATCH, DELETE. Маршруты, сериализация через Symfony Serializer, валидация через Validator и OpenAPI-документ появляются без написания контроллеров вручную.
Минимальная настройка
Установка через Composer (внутри контейнера):
composer require api
Сущность с атрибутом #[ApiResource]:
<?php
// src/Entity/Product.php
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;
#[ApiResource(
operations: [
new GetCollection(),
new Get(),
new Post(),
new Put(),
new Delete(),
]
)]
#[ORM\Entity]
class Product
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(max: 255)]
public string $name = '';
#[ORM\Column]
#[Assert\PositiveOrZero]
public float $price = 0.0;
public function getId(): ?int { return $this->id; }
}
После миграции (bin/console doctrine:migrations:migrate) сразу доступны все шесть эндпоинтов, а по адресу /api открывается Swagger UI.
Кастомная операция с StateProcessor
Когда стандартной персистентности через Doctrine недостаточно — например, нужно отправить email после создания заказа — подключают кастомный процессор:
<?php
// src/State/OrderProcessor.php
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Order;
use App\Service\Mailer;
final class OrderProcessor implements ProcessorInterface
{
public function __construct(
private ProcessorInterface $persistProcessor, // декорируем стандартный
private Mailer $mailer,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Order
{
/** @var Order $order */
$order = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
$this->mailer->sendOrderConfirmation($order);
return $order;
}
}
#[ApiResource(
operations: [
new Post(processor: OrderProcessor::class),
]
)]
class Order { /* ... */ }
Фильтрация и пагинация
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
use ApiPlatform\Metadata\ApiFilter;
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: ['name' => 'partial'])]
#[ApiFilter(RangeFilter::class, properties: ['price'])]
class Product { /* ... */ }
Теперь работают запросы вида GET /products?name=phone&price[gte]=100. Пагинация включена по умолчанию (30 элементов на страницу), настраивается в config/packages/api_platform.yaml.
Подводные камни
- Circular reference при сериализации. Если сущности ссылаются друг на друга (Order → User → Order), Serializer уходит в бесконечную рекурсию. Решение — группы сериализации (
#[Groups(['order:read'])]) на каждом свойстве. - N+1 запросы в коллекциях. По умолчанию Doctrine подгружает связанные сущности по одной. Нужен
fetch: EAGERили кастомныйStateProviderсJOIN FETCH. - Конфликт маршрутов с обычными контроллерами. API Platform регистрирует маршруты с префиксом
/api; если у вас уже есть контроллер на этом пути, возникнет конфликт. Проверяйте черезbin/console debug:router. - Валидация срабатывает до процессора. Symfony Validator запускается автоматически перед
process(). Если добавить кастомный Constraint, который делает запрос в БД, он выполнится на каждый POST — учитывайте производительность. - Версионирование API. API Platform не поддерживает версионирование «из коробки». Придётся либо использовать разные префиксы (
/api/v1,/api/v2), либо content negotiation через custom operation. - GraphQL и Mercure требуют отдельных пакетов.
composer require webonyx/graphql-phpиcomposer require mercure— без них соответствующие разделы документации не работают, хотя атрибут#[ApiResource]уже создаёт ощущение поддержки.
Common mistakes
- Сводить api platform к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Symfony 7/8 строит request lifecycle вокруг HttpKernel, events, routing, controller resolver и response listeners.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет api platform через конкретную точку lifecycle в Symfony.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.