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

API Platform GraphQL przekształca aplikacje Symfony w wydajne, typowane interfejsy API, które rozwiązują problemy nadmiernego i niedostatecznego pobierania danych charakterystyczne dla REST. Dzięki automatycznemu generowaniu schematów z atrybutów PHP i pełnej obsłudze specyfikacji Relay, API Platform 4.x dostarcza produkcyjną implementację GraphQL wymagającą minimalnej konfiguracji.
Zapytania GraphQL pobierają dokładnie potrzebne pola w pojedynczym żądaniu, podczas gdy REST zwraca ustalone struktury odpowiedzi. API Platform generuje oba endpointy z tej samej definicji zasobu, pozwalając klientom wybrać protokół odpowiedni dla danego przypadku użycia.
Instalacja i włączenie obsługi GraphQL w Symfony
API Platform wydziela funkcjonalność GraphQL do osobnego pakietu. Takie modułowe podejście utrzymuje rdzeń lekkim dla projektów potrzebujących wyłącznie REST.
# Instalacja obsługi GraphQL
composer require api-platform/graphqlPo zainstalowaniu endpoint /graphql staje się automatycznie dostępny. Schemat generuje się z istniejących atrybutów #[ApiResource] bez dodatkowej konfiguracji.
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;
// Gettery i settery...
}Ta pojedyncza definicja encji udostępnia zapytania do pobierania książek po ID lub jako kolekcji, plus mutacje dla operacji tworzenia, aktualizacji i usuwania. Schemat GraphQL odzwierciedla typy PHP bezpośrednio: string staje się String!, typy nullable stają się polami opcjonalnymi.
Pisanie zapytań i mutacji GraphQL
Zapytania GraphQL określają dokładnie, które pola mają być zwrócone. Ta precyzja eliminuje marnowanie przepustowości i redukuje transformacje danych po stronie klienta.
# Pobranie pojedynczej książki z określonymi polami
query GetBook {
book(id: "/books/42") {
title
isbn
publishedAt
}
}
# Pobranie kolekcji z paginacją
query ListBooks {
books(first: 10, after: "cursor123") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}Mutacje stosują specyfikację Relay z obiektami input i clientMutationId do śledzenia żądań.
# Utworzenie nowej książki
mutation CreateBook {
createBook(input: {
title: "Domain-Driven Design"
isbn: "9780321125217"
publishedAt: "2003-08-30"
clientMutationId: "create-1"
}) {
book {
id
title
}
clientMutationId
}
}
# Aktualizacja istniejącej książki
mutation UpdateBook {
updateBook(input: {
id: "/books/42"
title: "Zaktualizowany tytuł"
clientMutationId: "update-1"
}) {
book {
id
title
}
}
}clientMutationId pomaga klientom korelować odpowiedzi z żądaniami w scenariuszach wsadowych. API Platform zwraca go niezmienionego w odpowiedzi.
Implementacja własnych resolwerów dla złożonej logiki biznesowej
Domyślne operacje CRUD pokrywają podstawowe przypadki, ale prawdziwe aplikacje potrzebują własnej logiki biznesowej. API Platform dostarcza interfejsy resolwerów dla zapytań i mutacji.
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
{
// Dostęp do argumentów GraphQL z kontekstu
$limit = $context['args']['limit'] ?? 10;
$period = $context['args']['period'] ?? 'month';
return $this->bookRepository->findBestSellers($limit, $period);
}
}Rejestracja własnego resolwera w konfiguracji encji:
#[ApiResource(
graphQlOperations: [
new QueryCollection(
name: 'bestSellers',
resolver: BookBestSellerResolver::class,
args: [
'limit' => ['type' => 'Int', 'default_value' => 10],
'period' => ['type' => 'String', 'default_value' => 'month'],
]
),
]
)]
class Book
{
// ...
}To udostępnia zapytanie bestSellers, które przyjmuje argumenty limit i period, wykonując własną logikę repozytorium zamiast domyślnych zapytań Doctrine.
Gotowy na rozmowy o Symfony?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Zabezpieczanie operacji GraphQL z Voterami i wyrażeniami
Konfiguracja bezpieczeństwa dla GraphQL działa niezależnie od REST. Każda operacja może definiować własne reguły dostępu używając języka wyrażeń 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: "Tylko autor może aktualizować tę książkę."
),
new Mutation(
name: 'delete',
security: "is_granted('ROLE_ADMIN')"
),
]
)]
class Book
{
// ...
}Zmienna object w wyrażeniach bezpieczeństwa odnosi się do encji, do której uzyskiwany jest dostęp. Umożliwia to szczegółowe sprawdzanie własności. Dla złożonej logiki autoryzacji votery Symfony Security dostarczają czystsze rozwiązanie niż wyrażenia 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,
};
}
}Aktualizacje w czasie rzeczywistym z subskrypcjami GraphQL
API Platform implementuje subskrypcje GraphQL przez Mercure, protokół dla zdarzeń wysyłanych przez serwer. Subskrypcje przekazują dane do klientów, gdy zasoby się zmieniają.
use ApiPlatform\Metadata\GraphQl\Subscription;
#[ApiResource(
mercure: true,
graphQlOperations: [
new Query(),
new Mutation(name: 'update'),
new Subscription(),
]
)]
class Book
{
// ...
}Klienci subskrybują zmiany używając standardowej składni subskrypcji GraphQL:
subscription BookUpdates {
updateBookSubscribe(input: { id: "/books/42" }) {
book {
id
title
updatedAt
}
}
}Kiedy mutacja aktualizuje książkę, Mercure rozgłasza zmianę do wszystkich zasubskrybowanych klientów. Ten wzorzec pasuje do aplikacji współpracy, paneli na żywo i powiadomień w czasie rzeczywistym.
Pytania rekrutacyjne: API Platform GraphQL
Rozmowy techniczne na stanowiska Symfony coraz częściej obejmują integrację GraphQL. Te pytania testują zrozumienie zarówno specyfikacji, jak i implementacji API Platform.
P: Jak API Platform generuje schemat GraphQL?
API Platform introspektuje atrybuty #[ApiResource] i deklaracje typów PHP, aby zbudować schemat. Właściwości encji stają się polami, z typami PHP mapowanymi na typy GraphQL. Schemat regeneruje się przy każdym żądaniu w trybie deweloperskim i cache'uje w produkcji.
P: Jaka jest różnica między operacjami Query i QueryCollection?
Query pobiera pojedynczy element po identyfikatorze i wymaga argumentu id. QueryCollection zwraca wiele elementów z opcjonalnym filtrowaniem, paginacją i sortowaniem. Oba mogą mieć własne resolwery, ale ich interfejsy się różnią: QueryItemResolverInterface vs QueryCollectionResolverInterface.
P: Jak obsłużyć problemy N+1 zapytań w API Platform GraphQL?
Wzorzec DataLoader grupuje wiele zapytań do bazy danych w jedno. API Platform integruje się z eager loading Doctrine przez fetch joins w rozszerzeniu zapytania. Dla złożonych przypadków należy zaimplementować własny resolwer używający addSelect() Doctrine do pobierania asocjacji w początkowym zapytaniu.
// Własne rozszerzenie zapytania dla eager loading
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
$queryBuilder
->addSelect('author')
->leftJoin('o.author', 'author');
}P: Czy reguły bezpieczeństwa REST i GraphQL mogą się różnić dla tego samego zasobu?
Tak. Operacje REST używają security na #[Get], #[Post], itd., podczas gdy operacje GraphQL używają security na #[Query], #[Mutation], itd. Ta separacja pozwala na bardziej rygorystyczne reguły dla jednego protokołu. Częsty wzorzec udostępnia GraphQL tylko do odczytu dla publicznych klientów, podczas gdy mutacje REST wymagają uwierzytelnienia.
P: Jak dodać własne typy skalarne do schematu GraphQL?
Należy zarejestrować własny typ w config/packages/api_platform.yaml i zaimplementować logikę serializacji:
# 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);
}
}Strategie paginacji: kursorowa vs stronicowa
API Platform domyślnie stosuje paginację kursorową zgodną ze specyfikacją Relay Connection. To podejście lepiej obsługuje dane w czasie rzeczywistym niż paginacja offsetowa, ponieważ wstawienia nie przesuwają wyników.
# Kursorowa (domyślna)
query {
books(first: 10, after: "YXJyYXljb25uZWN0aW9uOjk=") {
edges {
cursor
node {
title
}
}
pageInfo {
endCursor
hasNextPage
}
}
}Paginacja stronicowa pasuje do prostszych przypadków użycia, gdzie klienci potrzebują bezpośredniego dostępu do stron:
// Włączenie paginacji stronicowej
#[ApiResource(
paginationType: 'page',
graphQlOperations: [
new QueryCollection(paginationType: 'page'),
]
)]
class Book {}# Stronicowa
query {
books(page: 2, itemsPerPage: 20) {
collection {
title
}
paginationInfo {
totalCount
lastPage
}
}
}Paginacja kursorowa działa lepiej na dużą skalę, ponieważ unika zapytań OFFSET. Kompromisem jest to, że klienci nie mogą przeskakiwać do dowolnych stron.
Testowanie endpointów GraphQL w Symfony
Testy funkcjonalne weryfikują zachowanie GraphQL używając klienta testowego Symfony. API Platform dostarcza dedykowany trait testowy dla 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();
// Utworzenie danych testowych
$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();
// Wykonanie zapytania 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);
}
}Te testy walidują zarówno pomyślne operacje, jak i egzekwowanie bezpieczeństwa. Uruchomienie ich poleceniem php bin/phpunit tests/GraphQL/ pozwala wychwycić regresje w zachowaniu API.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Kluczowe wnioski dla API Platform GraphQL w Symfony
- Zainstalowanie
api-platform/graphqlwłącza endpoint/graphqlz automatycznym generowaniem schematu z atrybutów#[ApiResource] - Użycie operacji
Query,QueryCollectioniMutationkontroluje, które operacje GraphQL każdy zasób udostępnia - Implementacja
QueryItemResolverInterfacelubMutationResolverInterfaceobsługuje własną logikę biznesową wykraczającą poza CRUD - Wyrażenia bezpieczeństwa na operacjach GraphQL działają niezależnie od REST, pozwalając na różne reguły dostępu na protokół
- Włączenie Mercure umożliwia subskrypcje w czasie rzeczywistym, które przekazują zmiany do klientów, gdy zasoby się aktualizują
- Paginacja kursorowa obsługuje dane w czasie rzeczywistym lepiej niż stronicowa, ale rezygnuje z bezpośredniego dostępu do stron
- Testowanie endpointów GraphQL z
ApiTestCasei żądaniami JSON POST do/graphql - Pytania rekrutacyjne koncentrują się na generowaniu schematu, problemach N+1, separacji bezpieczeństwa i własnych resolwerach
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 27 sierpnia 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 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.