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

API Platform GraphQL перетворює додатки Symfony на потужні, типобезпечні API, що вирішують проблеми надмірного та недостатнього отримання даних, притаманні REST. Завдяки автоматичній генерації схем з PHP-атрибутів та повній підтримці специфікації Relay, API Platform 4.x надає готову до продакшену реалізацію GraphQL, що вимагає мінімальної конфігурації.
Запити GraphQL отримують саме ті поля, які потрібні, в одному запиті, тоді як REST повертає фіксовані структури відповідей. API Platform генерує обидва ендпоінти з однієї дефініції ресурсу, дозволяючи клієнтам обирати протокол, що підходить для їхнього випадку використання.
Встановлення та увімкнення підтримки GraphQL у Symfony
API Platform виділяє функціональність GraphQL в окремий пакет. Такий модульний підхід зберігає ядро легким для проєктів, яким потрібен лише REST.
# Встановлення підтримки GraphQL
composer require api-platform/graphqlПісля встановлення ендпоінт /graphql стає автоматично доступним. Схема генерується з наявних атрибутів #[ApiResource] без додаткової конфігурації.
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Mutation;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource(
graphQlOperations: [
new Query(),
new QueryCollection(),
new Mutation(name: 'create'),
new Mutation(name: 'update'),
new Mutation(name: 'delete'),
]
)]
class Book
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $title;
#[ORM\Column(length: 13)]
private string $isbn;
#[ORM\Column]
private \DateTimeImmutable $publishedAt;
// Гетери та сетери...
}Ця єдина дефініція сутності надає запити для отримання книг за ID або як колекції, плюс мутації для операцій створення, оновлення та видалення. Схема GraphQL безпосередньо відображає типи PHP: string стає String!, nullable-типи стають опціональними полями.
Написання запитів та мутацій GraphQL
Запити GraphQL точно вказують, які поля повертати. Ця точність усуває марнування пропускної здатності та зменшує трансформації даних на стороні клієнта.
# Отримання однієї книги з конкретними полями
query GetBook {
book(id: "/books/42") {
title
isbn
publishedAt
}
}
# Отримання колекції з пагінацією
query ListBooks {
books(first: 10, after: "cursor123") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}Мутації дотримуються специфікації Relay з об'єктами input та clientMutationId для відстеження запитів.
# Створення нової книги
mutation CreateBook {
createBook(input: {
title: "Domain-Driven Design"
isbn: "9780321125217"
publishedAt: "2003-08-30"
clientMutationId: "create-1"
}) {
book {
id
title
}
clientMutationId
}
}
# Оновлення існуючої книги
mutation UpdateBook {
updateBook(input: {
id: "/books/42"
title: "Оновлена назва"
clientMutationId: "update-1"
}) {
book {
id
title
}
}
}clientMutationId допомагає клієнтам співвідносити відповіді із запитами в пакетних сценаріях. API Platform повертає його незмінним у відповіді.
Реалізація власних резолверів для складної бізнес-логіки
Стандартні CRUD-операції покривають базові випадки, але реальні додатки потребують власної бізнес-логіки. API Platform надає інтерфейси резолверів для запитів та мутацій.
namespace App\Resolver;
use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface;
use App\Repository\BookRepository;
final class BookBestSellerResolver implements QueryCollectionResolverInterface
{
public function __construct(
private readonly BookRepository $bookRepository
) {}
/**
* @param iterable<Book> $collection
* @return iterable<Book>
*/
public function __invoke(iterable $collection, array $context): iterable
{
// Доступ до аргументів GraphQL з контексту
$limit = $context['args']['limit'] ?? 10;
$period = $context['args']['period'] ?? 'month';
return $this->bookRepository->findBestSellers($limit, $period);
}
}Реєстрація власного резолвера в конфігурації сутності:
#[ApiResource(
graphQlOperations: [
new QueryCollection(
name: 'bestSellers',
resolver: BookBestSellerResolver::class,
args: [
'limit' => ['type' => 'Int', 'default_value' => 10],
'period' => ['type' => 'String', 'default_value' => 'month'],
]
),
]
)]
class Book
{
// ...
}Це надає запит bestSellers, що приймає аргументи limit та period, виконуючи власну логіку репозиторію замість стандартних запитів Doctrine.
Готовий до співбесід з Symfony?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Захист операцій GraphQL за допомогою Voter'ів та виразів
Конфігурація безпеки для GraphQL працює незалежно від REST. Кожна операція може визначати власні правила доступу, використовуючи мову виразів Symfony.
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\Mutation;
#[ApiResource(
graphQlOperations: [
new Query(
security: "is_granted('ROLE_USER')"
),
new QueryCollection(
security: "is_granted('ROLE_USER')"
),
new Mutation(
name: 'create',
security: "is_granted('ROLE_EDITOR')"
),
new Mutation(
name: 'update',
security: "is_granted('ROLE_EDITOR') and object.getAuthor() == user",
securityMessage: "Лише автор може оновлювати цю книгу."
),
new Mutation(
name: 'delete',
security: "is_granted('ROLE_ADMIN')"
),
]
)]
class Book
{
// ...
}Змінна object у виразах безпеки посилається на сутність, до якої здійснюється доступ. Це дозволяє детальні перевірки власності. Для складної логіки авторизації Symfony Security voter'и забезпечують чистіше рішення, ніж inline-вирази.
namespace App\Security\Voter;
use App\Entity\Book;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
class BookVoter extends Voter
{
public const EDIT = 'BOOK_EDIT';
public const DELETE = 'BOOK_DELETE';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE])
&& $subject instanceof Book;
}
protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool {
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
/** @var Book $book */
$book = $subject;
return match($attribute) {
self::EDIT => $book->getAuthor() === $user,
self::DELETE => in_array('ROLE_ADMIN', $user->getRoles()),
default => false,
};
}
}Оновлення в реальному часі з GraphQL Subscriptions
API Platform реалізує GraphQL subscriptions через Mercure — протокол для подій, що надсилаються сервером. Subscriptions передають дані клієнтам, коли ресурси змінюються.
use ApiPlatform\Metadata\GraphQl\Subscription;
#[ApiResource(
mercure: true,
graphQlOperations: [
new Query(),
new Mutation(name: 'update'),
new Subscription(),
]
)]
class Book
{
// ...
}Клієнти підписуються на зміни, використовуючи стандартний синтаксис GraphQL subscription:
subscription BookUpdates {
updateBookSubscribe(input: { id: "/books/42" }) {
book {
id
title
updatedAt
}
}
}Коли мутація оновлює книгу, Mercure транслює зміну всім підписаним клієнтам. Цей патерн підходить для додатків співпраці, живих дашбордів та сповіщень у реальному часі.
Питання на співбесіді: API Platform GraphQL
Технічні співбесіди на позиції Symfony все частіше охоплюють інтеграцію GraphQL. Ці питання перевіряють розуміння як специфікації, так і реалізації API Platform.
П: Як API Platform генерує схему GraphQL?
API Platform досліджує атрибути #[ApiResource] та оголошення типів PHP для побудови схеми. Властивості сутності стають полями, з типами PHP, що відображаються на типи GraphQL. Схема регенерується при кожному запиті в режимі розробки та кешується в продакшені.
П: Яка різниця між операціями Query та QueryCollection?
Query отримує один елемент за ідентифікатором і вимагає аргументу id. QueryCollection повертає декілька елементів з опціональною фільтрацією, пагінацією та сортуванням. Обидві можуть мати власні резолвери, але їхні інтерфейси відрізняються: QueryItemResolverInterface та QueryCollectionResolverInterface.
П: Як обробляти проблеми N+1 запитів в API Platform GraphQL?
Патерн DataLoader групує декілька запитів до бази даних в один. API Platform інтегрується з eager loading Doctrine через fetch joins у розширенні запиту. Для складних випадків слід реалізувати власний резолвер, що використовує addSelect() Doctrine для отримання асоціацій у початковому запиті.
// Власне розширення запиту для eager loading
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
$queryBuilder
->addSelect('author')
->leftJoin('o.author', 'author');
}П: Чи можуть правила безпеки REST та GraphQL відрізнятися для одного ресурсу?
Так. Операції REST використовують security на #[Get], #[Post] тощо, тоді як операції GraphQL використовують security на #[Query], #[Mutation] тощо. Це розділення дозволяє встановлювати суворіші правила для одного протоколу. Поширений патерн надає GraphQL лише для читання публічним клієнтам, тоді як мутації REST вимагають автентифікації.
П: Як додати власні скалярні типи до схеми GraphQL?
Потрібно зареєструвати власний тип у config/packages/api_platform.yaml та реалізувати логіку серіалізації:
# config/packages/api_platform.yaml
api_platform:
graphql:
enabled: true
graphiql:
enabled: truenamespace App\GraphQL\Type;
use GraphQL\Type\Definition\ScalarType;
final class DateTimeType extends ScalarType
{
public string $name = 'DateTime';
public function serialize($value): string
{
return $value->format(\DateTimeInterface::RFC3339);
}
public function parseValue($value): \DateTimeImmutable
{
return new \DateTimeImmutable($value);
}
public function parseLiteral($valueNode, ?array $variables = null): \DateTimeImmutable
{
return new \DateTimeImmutable($valueNode->value);
}
}Стратегії пагінації: курсорна vs сторінкова
API Platform за замовчуванням використовує курсорну пагінацію згідно зі специфікацією Relay Connection. Цей підхід краще обробляє дані в реальному часі, ніж offset-пагінація, оскільки вставки не зміщують результати.
# Курсорна (за замовчуванням)
query {
books(first: 10, after: "YXJyYXljb25uZWN0aW9uOjk=") {
edges {
cursor
node {
title
}
}
pageInfo {
endCursor
hasNextPage
}
}
}Сторінкова пагінація підходить для простіших випадків використання, де клієнтам потрібен прямий доступ до сторінок:
// Увімкнення сторінкової пагінації
#[ApiResource(
paginationType: 'page',
graphQlOperations: [
new QueryCollection(paginationType: 'page'),
]
)]
class Book {}# Сторінкова
query {
books(page: 2, itemsPerPage: 20) {
collection {
title
}
paginationInfo {
totalCount
lastPage
}
}
}Курсорна пагінація працює краще на масштабі, оскільки уникає запитів OFFSET. Компроміс полягає в тому, що клієнти не можуть переходити до довільних сторінок.
Тестування ендпоінтів GraphQL у Symfony
Функціональні тести верифікують поведінку GraphQL, використовуючи тестовий клієнт Symfony. API Platform надає спеціалізований trait для тестування GraphQL.
namespace App\Tests\GraphQL;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;
class BookTest extends ApiTestCase
{
public function testQueryBook(): void
{
$client = static::createClient();
// Створення тестових даних
$book = new Book();
$book->setTitle('Test Book');
$book->setIsbn('1234567890123');
$book->setPublishedAt(new \DateTimeImmutable());
$em = static::getContainer()->get('doctrine')->getManager();
$em->persist($book);
$em->flush();
// Виконання запиту GraphQL
$response = $client->request('POST', '/graphql', [
'json' => [
'query' => '
query GetBook($id: ID!) {
book(id: $id) {
title
isbn
}
}
',
'variables' => [
'id' => '/books/' . $book->getId(),
],
],
]);
$this->assertResponseIsSuccessful();
$data = $response->toArray();
$this->assertEquals('Test Book', $data['data']['book']['title']);
$this->assertEquals('1234567890123', $data['data']['book']['isbn']);
}
public function testMutationRequiresAuthentication(): void
{
$client = static::createClient();
$response = $client->request('POST', '/graphql', [
'json' => [
'query' => '
mutation CreateBook {
createBook(input: {
title: "Unauthorized Book"
isbn: "0000000000000"
clientMutationId: "test"
}) {
book { id }
}
}
',
],
]);
$data = $response->toArray();
$this->assertArrayHasKey('errors', $data);
}
}Ці тести валідують як успішні операції, так і застосування безпеки. Запуск їх командою php bin/phpunit tests/GraphQL/ дозволяє виявити регресії в поведінці API.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Ключові висновки для API Platform GraphQL у Symfony
- Встановлення
api-platform/graphqlвмикає ендпоінт/graphqlз автоматичною генерацією схеми з атрибутів#[ApiResource] - Використання операцій
Query,QueryCollectionтаMutationконтролює, які операції GraphQL надає кожен ресурс - Реалізація
QueryItemResolverInterfaceабоMutationResolverInterfaceобробляє власну бізнес-логіку за межами CRUD - Вирази безпеки на операціях GraphQL працюють незалежно від REST, дозволяючи різні правила доступу для кожного протоколу
- Увімкнення Mercure забезпечує subscriptions у реальному часі, що передають зміни клієнтам при оновленні ресурсів
- Курсорна пагінація обробляє дані в реальному часі краще, ніж сторінкова, але втрачає прямий доступ до сторінок
- Тестування ендпоінтів GraphQL з
ApiTestCaseта JSON POST-запитами до/graphql - Питання на співбесіді фокусуються на генерації схеми, проблемах N+1, розділенні безпеки та власних резолверах
Чи знайдеш ти помилку в Symfony?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

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

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

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

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