# 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. - Published: 2026-08-27 - Updated: 2026-08-27 - Author: Anthony Fillion-Maillet - Reading time: 8 min --- 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. > **Differenza Chiave: GraphQL vs REST in API Platform** > > 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. ```bash # Installa il supporto GraphQL composer require api-platform/graphql ``` Una volta installato, l'endpoint `/graphql` diventa automaticamente disponibile. Lo schema viene generato dagli attributi `#[ApiResource]` esistenti senza configurazione aggiuntiva. ```php // src/Entity/Book.php 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. ```graphql # 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. ```graphql # 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. ```php // src/Resolver/BookBestSellerResolver.php 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 $collection * @return iterable */ 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à: ```php // src/Entity/Book.php #[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. ## 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. ```php // src/Entity/Book.php 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. ```php // src/Security/Voter/BookVoter.php 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. ```php // src/Entity/Book.php 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: ```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. ```graphql # 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: ```php // src/Entity/Book.php 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 { // ... } ``` ```graphql # 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. ```php // src/Entity/Book.php 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: ```json { "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. ## 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: ```yaml # 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: ```php #[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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/symfony/api-platform-graphql-symfony-schemas-mutations-interview