API Platform GraphQL con Symfony: Schema, Mutation e Domande da Colloquio 2026
Guida completa ad API Platform GraphQL con Symfony: generazione schema, query, mutation, resolver personalizzati, sicurezza e domande tecniche per colloqui 2026.

API Platform GraphQL trasforma le applicazioni Symfony in API potenti e type-safe, risolvendo i problemi di over-fetching e under-fetching tipici delle API REST. Con la generazione automatica dello schema da attributi PHP e il supporto completo alla specifica Relay, API Platform 4.x offre un'implementazione GraphQL pronta per la produzione che richiede una configurazione minima.
GraphQL richiede esattamente i campi necessari in una singola query, mentre REST restituisce strutture di risposta fisse. API Platform genera entrambi gli endpoint dalla stessa definizione di risorsa, permettendo ai client di scegliere il protocollo più adatto al proprio caso d'uso.
Installazione e Abilitazione del Supporto GraphQL in Symfony
API Platform separa la funzionalità GraphQL in un pacchetto dedicato. Questo approccio modulare mantiene il core leggero per i progetti che necessitano solo di REST.
# Installa il supporto GraphQL
composer require api-platform/graphqlUna volta installato, l'endpoint /graphql diventa automaticamente disponibile. Lo schema viene generato dagli attributi #[ApiResource] esistenti senza configurazione aggiuntiva.
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;
// Getter e setter...
}Questa singola definizione di entità espone query per recuperare libri per ID o come collezioni, più mutation per operazioni di creazione, aggiornamento ed eliminazione. Lo schema GraphQL riflette direttamente i tipi PHP: string diventa String!, i tipi nullable diventano campi opzionali.
Scrittura di Query e Mutation GraphQL
Le query GraphQL specificano esattamente quali campi restituire. Questa precisione elimina lo spreco di banda e riduce le trasformazioni dei dati lato client.
# Recupera un singolo libro con campi specifici
query GetBook {
book(id: "/books/42") {
title
isbn
publishedAt
}
}
# Recupera una collezione con paginazione
query ListBooks {
books(first: 10, after: "cursor123") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}Le mutation seguono la specifica Relay con oggetti input e clientMutationId per il tracciamento delle richieste.
# Crea un nuovo libro
mutation CreateBook {
createBook(input: {
title: "Domain-Driven Design"
isbn: "9780321125217"
publishedAt: "2003-08-30"
clientMutationId: "create-1"
}) {
book {
id
title
}
clientMutationId
}
}
# Aggiorna un libro esistente
mutation UpdateBook {
updateBook(input: {
id: "/books/42"
title: "Titolo Aggiornato"
clientMutationId: "update-1"
}) {
book {
id
title
}
}
}Il clientMutationId aiuta i client a correlare le risposte con le richieste in scenari batch. API Platform lo restituisce invariato nella risposta.
Implementazione di Resolver Personalizzati per Logica di Business Complessa
Le operazioni CRUD standard coprono i casi base, ma le applicazioni reali necessitano di logica di business personalizzata. API Platform fornisce interfacce resolver per query e mutation.
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
{
// Accede agli argomenti GraphQL dal contesto
$limit = $context['args']['limit'] ?? 10;
$period = $context['args']['period'] ?? 'month';
return $this->bookRepository->findBestSellers($limit, $period);
}
}Il resolver personalizzato viene registrato nella configurazione dell'entità:
#[ApiResource(
graphQlOperations: [
new QueryCollection(
name: 'bestSellers',
resolver: BookBestSellerResolver::class,
args: [
'limit' => ['type' => 'Int', 'default_value' => 10],
'period' => ['type' => 'String', 'default_value' => 'month'],
]
),
]
)]
class Book
{
// ...
}Questo espone una query bestSellers che accetta argomenti limit e period, eseguendo logica repository personalizzata invece delle query Doctrine predefinite.
Pronto a superare i tuoi colloqui su Symfony?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Protezione delle Operazioni GraphQL con Voter ed Espressioni
La configurazione della sicurezza per GraphQL opera indipendentemente da REST. Ogni operazione può definire le proprie regole di accesso usando il linguaggio delle espressioni di 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: "Solo l'autore può aggiornare questo libro."
),
new Mutation(
name: 'delete',
security: "is_granted('ROLE_ADMIN')"
),
]
)]
class Book
{
// ...
}La variabile object nelle espressioni di sicurezza si riferisce all'entità a cui si accede. Questo permette controlli di proprietà granulari. Per logica di autorizzazione complessa, i Voter di Symfony Security forniscono una soluzione più pulita rispetto alle espressioni 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,
};
}
}Aggiornamenti in Tempo Reale con GraphQL Subscription
API Platform implementa le subscription GraphQL tramite Mercure, un protocollo per server-sent events. Le subscription inviano dati ai client quando le risorse cambiano.
use ApiPlatform\Metadata\GraphQl\Subscription;
#[ApiResource(
mercure: true,
graphQlOperations: [
new Query(),
new Mutation(name: 'update'),
new Subscription(),
]
)]
class Book
{
// ...
}I client si sottoscrivono alle modifiche usando la sintassi standard delle subscription GraphQL:
subscription BookUpdates {
updateBookSubscribe(input: { id: "/books/42" }) {
book {
id
title
updatedAt
}
}
}Quando una mutation aggiorna il libro, Mercure trasmette la modifica a tutti i client sottoscritti. Questo pattern è adatto per applicazioni collaborative, dashboard live e notifiche in tempo reale.
Paginazione e Filtraggio nelle Query GraphQL
API Platform implementa di default la paginazione basata su cursore conforme a Relay. Questo approccio fornisce risultati consistenti anche con dataset in evoluzione.
# Recupera la prima pagina
query FirstPage {
books(first: 20) {
edges {
node {
id
title
author {
name
}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
# Recupera la pagina successiva con cursore
query NextPage {
books(first: 20, after: "YXJyYXljb25uZWN0aW9uOjE5") {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Il filtraggio utilizza la stessa configurazione dei filtri REST, ma viene applicato tramite argomenti della query:
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: ['title' => 'partial', 'author.name' => 'exact'])]
#[ApiFilter(DateFilter::class, properties: ['publishedAt'])]
#[ApiFilter(OrderFilter::class, properties: ['title', 'publishedAt'])]
class Book
{
// ...
}# Query filtrata con ordinamento
query FilteredBooks {
books(
title: "Symfony"
publishedAt: { after: "2024-01-01" }
order: { publishedAt: "DESC" }
first: 10
) {
edges {
node {
title
publishedAt
}
}
}
}Gestione degli Errori e Validazione
API Platform integra perfettamente i constraint del Symfony Validator con le mutation GraphQL. Gli errori di validazione vengono restituiti come errori GraphQL strutturati.
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ApiResource]
class Book
{
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message: 'Il titolo non può essere vuoto.')]
#[Assert\Length(max: 255, maxMessage: 'Il titolo non può superare 255 caratteri.')]
private string $title;
#[ORM\Column(length: 13)]
#[Assert\Isbn(message: 'Formato ISBN non valido.')]
private string $isbn;
#[ORM\Column]
#[Assert\NotNull]
#[Assert\LessThanOrEqual('today', message: 'La data di pubblicazione non può essere nel futuro.')]
private \DateTimeImmutable $publishedAt;
}Quando si verificano errori di validazione, API Platform restituisce dettagli di errore strutturati:
{
"errors": [
{
"message": "title: Il titolo non può essere vuoto.",
"extensions": {
"category": "user",
"violations": [
{
"path": "title",
"message": "Il titolo non può essere vuoto."
}
]
}
}
]
}Domande da Colloquio: API Platform GraphQL
I colloqui tecnici per posizioni Symfony trattano sempre più l'integrazione GraphQL. Queste domande testano la comprensione sia della specifica che dell'implementazione di API Platform.
D: Come genera API Platform lo schema GraphQL?
API Platform analizza gli attributi #[ApiResource] e le dichiarazioni dei tipi PHP per costruire lo schema. Le proprietà delle entità diventano campi, con i tipi PHP mappati ai tipi GraphQL. Lo schema viene rigenerato ad ogni richiesta in modalità sviluppo e memorizzato in cache in produzione.
D: Qual è la differenza tra operazioni Query e QueryCollection?
Query recupera un singolo elemento per identificatore e richiede un argomento id. QueryCollection restituisce più elementi con filtraggio, paginazione e ordinamento opzionali. Entrambi possono avere resolver personalizzati, ma le loro interfacce differiscono: QueryItemResolverInterface vs. QueryCollectionResolverInterface.
D: Come vengono risolte le relazioni tra entità in GraphQL?
API Platform risolve automaticamente le relazioni quando vengono richieste come campi. Le relazioni Doctrine (ManyToOne, OneToMany) sono rappresentate come tipi annidati nello schema. Il problema delle N+1 query viene ottimizzato automaticamente dai Data Loader, che raggruppano multiple query di relazione.
D: Quando utilizzare un resolver personalizzato invece delle operazioni CRUD standard?
I resolver personalizzati sono necessari quando la logica di business va oltre il semplice recupero o persistenza. Esempi includono: query aggregate, query con calcoli complessi, integrazione con API esterne o operazioni specifiche del dominio che non seguono il pattern CRUD.
D: Come funzionano le subscription GraphQL con Mercure?
Quando un client stabilisce una subscription, API Platform si connette a un hub Mercure. Durante le mutation, il server pubblica aggiornamenti tramite Mercure, che vengono trasmessi a tutti i client sottoscritti. Questo richiede un hub Mercure in esecuzione e la configurazione della variabile d'ambiente MERCURE_URL.
D: Come differisce la configurazione della sicurezza tra REST e GraphQL?
REST e GraphQL possono avere regole di sicurezza diverse per la stessa risorsa. Le operazioni GraphQL sono configurate separatamente in graphQlOperations, mentre le operazioni REST sono definite in operations. Questo permette diversi livelli di accesso a seconda del protocollo.
D: Come viene gestita la validazione nelle mutation GraphQL?
I constraint del Symfony Validator vengono eseguiti automaticamente prima della persistenza. Gli errori di validazione vengono restituiti come errori GraphQL con categoria "user" e informazioni dettagliate sulle violazioni. I client possono analizzare questi errori e mostrare messaggi di errore specifici per campo.
Pronto a superare i tuoi colloqui su Symfony?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Ottimizzazione delle Performance per API GraphQL
Le API GraphQL richiedono strategie di ottimizzazione specifiche a causa della loro struttura di query flessibile.
Query Complexity Limiting previene query che consumano troppe risorse:
# config/packages/api_platform.yaml
api_platform:
graphql:
enabled: true
graphiql:
enabled: '%kernel.debug%'
default_ide: graphiql
nesting_separator: '_'
collection:
pagination:
enabled: true
introspection:
enabled: '%kernel.debug%'Field-Level Caching sfrutta la cache HTTP di Symfony:
#[ApiResource(
graphQlOperations: [
new Query(
cacheHeaders: [
'max_age' => 3600,
'shared_max_age' => 7200,
]
),
]
)]Persisted Queries riducono la dimensione del payload sostituendo le stringhe delle query con hash. Questo è particolarmente utile per applicazioni mobile con banda limitata.
Conclusione
API Platform GraphQL fornisce un'implementazione GraphQL completa per applicazioni Symfony con uno sforzo di configurazione minimo. La generazione automatica dello schema dagli attributi PHP, la paginazione conforme a Relay e l'integrazione perfetta con Symfony Security lo rendono una soluzione pronta per la produzione.
I punti chiave per sviluppatori e candidati ai colloqui:
- GraphQL e REST possono essere serviti dalla stessa definizione
#[ApiResource], con ogni protocollo configurabile indipendentemente - I resolver personalizzati permettono logica di business complessa oltre le operazioni CRUD standard
- Le espressioni di sicurezza e i voter forniscono controllo degli accessi flessibile e granulare
- Le subscription basate su Mercure permettono aggiornamenti in tempo reale senza la complessità dei WebSocket
- Validazione, filtraggio e paginazione funzionano perfettamente con la specifica GraphQL
Padroneggiare questi concetti è essenziale per gli sviluppatori Symfony moderni, poiché le API GraphQL stanno diventando sempre più comuni nelle applicazioni enterprise.
Sapresti trovare il bug in Symfony?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 27 agosto 2026
Condividi
Articoli correlati

Sicurezza REST API Symfony nel 2026: OAuth2, Rate Limiting e Domande per Colloqui
Guida completa alla protezione delle REST API Symfony con OAuth2 Token Introspection, componente RateLimiter, Voters e best practice di sicurezza.

API Platform con Symfony nel 2026: Architettura e Domande per Colloqui Tecnici
Guida completa ad API Platform con Symfony nel 2026. Architettura REST API, State Providers, Processors e domande frequenti nei colloqui per sviluppatori Symfony.

Sicurezza REST API Symfony: Autenticazione JWT e Best Practice 2026
Guida completa alla protezione delle REST API Symfony con autenticazione JWT, LexikJWTAuthenticationBundle e pratiche di sicurezza per applicazioni production-ready.