Resolver, mutazioni e sottoscrizioni
Recuperi e modifichi i dati tramite i resolver
Resolver, mutazioni e sottoscrizioni è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.
I resolver contengono la logica
Lo schema descrive che cosa esiste; i resolver decidono come viene prodotto il valore di ogni campo. Un resolver è semplicemente un callable. Le mutation sono resolver che modificano lo stato. Le subscription trasmettono valori nel tempo. Questa lezione tratta tutti e tre gli aspetti e il modello di esecuzione che li collega.
La firma del resolver
Ogni resolver riceve quattro argomenti: ($objectValue, $args, $context, ResolveInfo $info).
- $objectValue — il valore risolto del padre (
rootValueal livello superiore). - $args — gli argomenti del campo.
- $context — lo stato condiviso per richiesta (handle del DB, utente corrente).
- $info — i metadati AST/del campo (nome del campo, set di selezione, percorso).
<?php
use GraphQL\Type\Definition\ResolveInfo;
$resolve = function ($objectValue, array $args, $context, ResolveInfo $info) {
// $context['db'], $context['user'] set up per request
return $context['db']->find($args['id']);
};
Il resolver predefinito
Se non fornisce resolve, il resolver predefinito di graphql-php ricava il nome del campo dal valore padre: una chiave di array, una proprietà pubblica o un metodo get<Field>(). Ciò significa che spesso può risolvere interi tipi oggetto senza codice aggiuntivo, restituendo semplici array o DTO dal padre.
<?php
// Parent returns this array; child fields resolve by key automatically:
$user = [
'id' => 1,
'name' => 'Ada',
'email' => 'ada@example.com',
];
// 'name' field -> $user['name'] with no explicit resolver needed
var_dump($user['name']);
I resolver procedono dal padre al figlio
L'esecuzione procede dall'alto verso il basso: il resolver Query.user restituisce un utente, che diventa il $objectValue per User.posts, il cui risultato diventa il padre di ogni Post.title. Comprendere questo meccanismo a cascata è essenziale: è esattamente il punto in cui compare il problema N+1 (trattato nella prossima lezione).
<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
$userType = new ObjectType([
'name' => 'User',
'fields' => fn() => [
'id' => Type::id(),
'name' => Type::string(),
'posts' => [
'type' => Type::listOf(Type::string()),
// $user is the parent value resolved by Query.user
'resolve' => fn($user) => Posts::titlesForUser($user['id']),
],
],
]);
Restituire promise (asincrono)
I resolver possono restituire un valore oppure una promise. graphql-php include un adattatore di promise sincrone; con gli adattatori ReactPHP/Amp, la risoluzione può essere rinviata e raggruppata. Anche in modo sincrono, restituire oggetti Deferred consente all'executor di raccogliere il lavoro ed eseguirlo dopo il livello di risoluzione corrente: è il meccanismo su cui si basa DataLoader.
<?php
use GraphQL\Deferred;
$resolve = function ($post) use ($authorBuffer) {
$authorBuffer->add($post['author_id']); // queue the id
return new Deferred(function () use ($authorBuffer, $post) {
$authorBuffer->loadOnce(); // one batched query
return $authorBuffer->get($post['author_id']);
});
};
Le mutation modificano lo stato
Una Mutation è semplicemente un tipo radice chiamato Mutation. Per convenzione, i suoi campi di primo livello vengono eseguiti in sequenza (non in parallelo), così gli effetti collaterali restano ordinati. Gli input vengono in genere raggruppati in un InputObjectType per ottenere una firma chiara.
<?php
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;
$createPostInput = new InputObjectType([
'name' => 'CreatePostInput',
'fields' => [
'title' => Type::nonNull(Type::string()),
'body' => Type::string(),
],
]);
Collegare il tipo Mutation
Il campo mutation accetta l'oggetto di input come argomento e restituisce l'entità creata (così i client possono leggere i campi nello stesso round trip). Esegua la validazione e l'autorizzazione nel resolver, generando un'eccezione in caso di errore.
<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
$mutationType = new ObjectType([
'name' => 'Mutation',
'fields' => [
'createPost' => [
'type' => $postType,
'args' => ['input' => Type::nonNull($createPostInput)],
'resolve' => function ($root, array $args, $context) {
if (!$context['user']) {
throw new \RuntimeException('Unauthenticated');
}
return PostRepo::create($args['input'], $context['user']);
},
],
],
]);
Errori: sicuri per il client o interni
Per impostazione predefinita graphql-php nasconde i messaggi delle eccezioni e mostra Internal server error per evitare di divulgare dettagli interni. Per esporre un messaggio ai client, implementi GraphQL\Error\ClientAware e restituisca true da isClientSafe(). Aggiunga codici leggibili dalle macchine tramite extensions.
<?php
use GraphQL\Error\ClientAware;
class ValidationError extends \RuntimeException implements ClientAware {
public function isClientSafe(): bool { return true; }
// older versions also used getCategory(): string
}
Subscription: il concetto
Un tipo radice Subscription consente ai client di ricevere un flusso di risultati quando si verificano eventi (nuovo messaggio, variazione di prezzo). La specifica GraphQL definisce la semantica delle subscription, ma graphql-php esegue una singola operazione per chiamata: non esegue autonomamente un server socket di lunga durata. Deve fornire il trasporto.
- graphql-php risolve il payload della subscription per ogni evento inviato.
- Un trasporto (WebSocket tramite Ratchet/Mercure/Pusher) consegna gli eventi ai client.
Struttura di un resolver per le subscription
In pratica, si divide una subscription in un passaggio subscribe (registra l'interesse e restituisce una sorgente di eventi) e un passaggio resolve (mappa ogni evento al payload del campo). Molti stack PHP combinano graphql-php con Mercure o un broker pub/sub; il resolver seguente mostra la mappatura per evento di cui è responsabile graphql-php.
<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
$subscriptionType = new ObjectType([
'name' => 'Subscription',
'fields' => [
'messageAdded' => [
'type' => $messageType,
'args' => ['channelId' => Type::nonNull(Type::id())],
// graphql-php resolves each pushed event into the payload;
// a WebSocket/Mercure transport drives when this runs.
'resolve' => fn($event) => $event['message'],
],
],
]);
Il contesto è il canale per auth e DI
Il terzo argomento del resolver, $context, viene creato una volta per richiesta e passato a ogni resolver. È il posto giusto per l'utente autenticato, una connessione al database e i DataLoaders. Centralizzare l'autenticazione qui mantiene snelli i resolver: chiedono al contesto chi è l'utente invece di ricalcolarlo.
<?php
require 'vendor/autoload.php';
// Built once per HTTP request, passed to executeQuery():
$context = [
'user' => authenticate($_SERVER['HTTP_AUTHORIZATION'] ?? ''),
'db' => $pdo,
];
$resolve = function ($root, array $args, array $context) {
if ($context['user'] === null) {
throw new \RuntimeException('Unauthenticated');
}
return $context['db']->find($args['id']);
};
Verifica rapida
Come si rende visibile ai client GraphQL il messaggio di un'eccezione?
Riepilogo
Ha appreso il cuore del modello di esecuzione di GraphQL:
- I resolver ricevono
($value, $args, $context, $info); il resolver predefinito legge chiavi e getter dal padre. - La risoluzione procede dal padre al figlio: è l'origine del problema N+1.
- La restituzione di
Deferred/promise consente il batching. - Le mutation sono campi radice sequenziali che usano
InputObjectType; esegua l'autorizzazione e la validazione nel resolver. - Le subscription definiscono la risoluzione del payload, mentre il trasporto viene fornito dall'applicazione.
ClientAwarecontrolla quali messaggi di errore possono vedere i client.
Prossimo: eliminare il problema N+1 con DataLoader.
Domande Frequenti
La lezione «Resolver, mutazioni e sottoscrizioni» è gratuita?
Sì — il testo completo di «Resolver, mutazioni e sottoscrizioni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.
Cosa imparerò in «Resolver, mutazioni e sottoscrizioni»?
Recuperi e modifichi i dati tramite i resolver Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare PHP Academy?
Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Resolver, mutazioni e sottoscrizioni»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione PHP Academy?
Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- GraphQL a confronto con REST
- Creare uno schema con graphql-php
- Resolver, mutazioni e sottoscrizioni
- Prestazioni: N+1 e DataLoader