Resolutores, mutações e assinaturas
Busque e altere dados por meio de resolutores.
Resolutores, mutações e assinaturas é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.
A lógica fica nos resolvedores
O esquema descreve o que existe; os resolvedores decidem como o valor de cada campo é produzido. Um resolvedor é simplesmente algo chamável. Mutações são resolvedores que alteram o estado. Assinaturas transmitem valores ao longo do tempo. Esta lição aborda os três e o modelo de execução que os conecta.
A assinatura do resolvedor
Todo resolvedor recebe quatro argumentos: ($objectValue, $args, $context, ResolveInfo $info).
- $objectValue — o valor resolvido do pai (o
rootValueno nível superior). - $args — os argumentos do campo.
- $context — o estado compartilhado por requisição (conexão com o banco de dados, usuário atual).
- $info — metadados do AST/campo (nome do campo, conjunto de seleção, caminho).
<?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']);
};
O resolvedor padrão
Se você não fornecer resolve, o resolvedor padrão do graphql-php lê o nome do campo no valor pai: uma chave de matriz, uma propriedade pública ou um método get<Field>(). Isso significa que muitas vezes você pode resolver tipos de objeto inteiros sem código repetitivo, retornando matrizes simples ou objetos de transferência de dados do pai.
<?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']);
Os resolvedores se encadeiam do pai ao filho
A execução ocorre de cima para baixo: o resolvedor Query.user retorna um usuário, que se torna o $objectValue de User.posts, cujo resultado se torna o pai de cada Post.title. Compreender esse encadeamento é essencial — é exatamente aí que surge o problema N+1 (abordado na próxima lição).
<?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']),
],
],
]);
Retornando promessas (assíncrono)
Os resolvedores podem retornar um valor ou uma promessa. O graphql-php fornece um adaptador de promessas síncronas; com adaptadores ReactPHP/Amp, a resolução pode ser adiada e agrupada. Mesmo de forma síncrona, retornar objetos Deferred permite que o executor colete o trabalho e o execute após o nível atual de resolução — o mecanismo no qual o DataLoader se baseia.
<?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']);
});
};
Mutações alteram o estado
Uma mutação é simplesmente um tipo raiz chamado Mutation. Por convenção, seus campos de nível superior são executados sequencialmente (não em paralelo), para que os efeitos colaterais ocorram em ordem. As entradas normalmente são agrupadas em um InputObjectType para obter uma assinatura clara.
<?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(),
],
]);
Conectando o tipo de mutação
O campo de mutação recebe o objeto de entrada como argumento e retorna a entidade criada (para que os clientes possam ler os campos na mesma ida e volta). Realize a validação e a autorização dentro do resolvedor, lançando uma exceção em caso de falha.
<?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']);
},
],
],
]);
Erros: seguros para o cliente ou internos
Por padrão, o graphql-php oculta as mensagens de exceção, exibindo Internal server error para evitar o vazamento de detalhes internos. Para expor uma mensagem aos clientes, implemente GraphQL\Error\ClientAware e retorne true de isClientSafe(). Adicione códigos legíveis por máquina por meio de extensions.
<?php
use GraphQL\Error\ClientAware;
class ValidationError extends \RuntimeException implements ClientAware {
public function isClientSafe(): bool { return true; }
// older versions also used getCategory(): string
}
Assinaturas: o conceito
Um tipo raiz de assinatura permite que os clientes recebam um fluxo contínuo de resultados quando eventos ocorrem (nova mensagem, atualização de preço). A especificação do GraphQL define a semântica das assinaturas, mas o graphql-php executa uma única operação por chamada — ele não executa, por si só, um servidor de soquetes de longa duração. Você fornece o transporte.
- O graphql-php resolve a carga útil da assinatura para cada evento que você envia.
- Um transporte (WebSocket via Ratchet/Mercure/Pusher) entrega os eventos aos clientes.
Estrutura de um resolvedor de assinatura
Na prática, você divide uma assinatura em uma etapa de inscrição (registra o interesse e retorna uma fonte de eventos) e uma etapa de resolução (mapeia cada evento para a carga útil do campo). Muitas pilhas PHP combinam graphql-php com Mercure ou um intermediário de publicação/assinatura; o resolvedor abaixo mostra o mapeamento por evento pelo qual o graphql-php é responsável.
<?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'],
],
],
]);
O contexto é seu canal de autenticação e DI
O terceiro argumento do resolvedor, $context, é criado uma vez por requisição e passado a todos os resolvedores. Ele é o lugar certo para o usuário autenticado, uma conexão com o banco de dados e seus DataLoaders. Centralizar a autenticação aqui mantém os resolvedores enxutos — eles consultam o contexto para saber quem é o usuário, em vez de recalcular isso.
<?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ção rápida
Como tornar visível aos clientes do GraphQL a mensagem de uma exceção?
Recapitulação
Você aprendeu o núcleo da execução do GraphQL:
- Os resolvedores recebem
($value, $args, $context, $info); o resolvedor padrão lê chaves e métodos de acesso do pai. - A resolução se encadeia do pai ao filho — essa é a origem do N+1.
- Retornar
Deferred/promessas permite o agrupamento. - Mutações são campos raiz sequenciais que usam
InputObjectType; faça a autenticação e a validação no resolvedor. - Assinaturas definem a resolução da carga útil, enquanto você fornece o transporte.
ClientAwarecontrola quais mensagens de erro os clientes podem ver.
A seguir: eliminar o problema N+1 com DataLoader.
Perguntas Frequentes
A aula “Resolutores, mutações e assinaturas” é grátis?
Sim — o texto completo de “Resolutores, mutações e assinaturas” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.
O que vou aprender em “Resolutores, mutações e assinaturas”?
Busque e altere dados por meio de resolutores. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar PHP Academy?
Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Resolutores, mutações e assinaturas”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de PHP Academy?
Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- GraphQL versus REST
- Construindo um esquema com graphql-php
- Resolutores, mutações e assinaturas
- Desempenho: N+1 e DataLoader