0Pricing
PHP Academy · Lección

Resolvers, mutaciones y suscripciones

Obtenga y modifique datos mediante resolvers

Resolvers, mutaciones y suscripciones es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de PHP Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de PHP Academy incluye 4 lecciones en total.

Los resolvers contienen la lógica

El esquema describe qué existe; los resolvers deciden cómo se produce el valor de cada campo. Un resolver no es más que un callable. Las mutaciones son resolvers que cambian el estado. Las suscripciones transmiten valores a lo largo del tiempo. En esta lección se cubren los tres conceptos y el modelo de ejecución que los conecta.

La firma del resolver

Cada resolver recibe cuatro argumentos: ($objectValue, $args, $context, ResolveInfo $info).

  • $objectValue: el valor resuelto del padre (el rootValue en la parte superior).
  • $args: los argumentos del campo.
  • $context: el estado compartido por solicitud (la conexión a la base de datos, el usuario actual).
  • $info: los metadatos del AST y del campo (nombre del campo, conjunto de selección, ruta).
<?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']);
};

El resolver predeterminado

Si no proporciona resolve, el resolver predeterminado de graphql-php lee el nombre del campo del valor padre: una clave de array, una propiedad pública o un método get<Field>(). Esto significa que a menudo puede resolver tipos de objetos completos sin código repetitivo, devolviendo arrays simples o DTO desde el 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']);

Los resolvers se encadenan del padre al hijo

La ejecución se realiza de arriba abajo: el resolver de Query.user devuelve un usuario, que se convierte en el $objectValue de User.posts, cuyo resultado se convierte en el padre de cada Post.title. Entender esta secuencia es esencial: es exactamente donde aparece el problema N+1 (que se tratará en la siguiente lección).

<?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']),
        ],
    ],
]);

Devolver promesas (asincronía)

Los resolvers pueden devolver un valor o una promesa. graphql-php incluye un adaptador de promesas síncrono; con los adaptadores de ReactPHP/Amp, la resolución puede aplazarse y agruparse. Incluso de forma síncrona, devolver objetos Deferred permite que el ejecutor recopile el trabajo y lo ejecute después del nivel de resolución actual: es el mecanismo en el que se 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']);
    });
};

Las mutaciones cambian el estado

Una Mutation es simplemente un tipo raíz denominado Mutation. Por convención, sus campos de nivel superior se ejecutan secuencialmente (no en paralelo), de modo que los efectos secundarios mantienen un orden. Normalmente, las entradas se agrupan en un InputObjectType para obtener una firma 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(),
    ],
]);

Configurar el tipo Mutation

El campo de mutación acepta el objeto de entrada como argumento y devuelve la entidad creada, para que los clientes puedan leer sus campos en la misma ida y vuelta. Realice la validación y la autorización dentro del resolver y lance una excepción si fallan.

<?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']);
            },
        ],
    ],
]);

Errores: seguros para el cliente frente a internos

De forma predeterminada, graphql-php oculta los mensajes de las excepciones y muestra Internal server error para evitar revelar información interna. Para mostrar un mensaje a los clientes, implemente GraphQL\Error\ClientAware y devuelva true desde isClientSafe(). Añada códigos legibles por las máquinas mediante extensions.

<?php
use GraphQL\Error\ClientAware;

class ValidationError extends \RuntimeException implements ClientAware {
    public function isClientSafe(): bool { return true; }
    // older versions also used getCategory(): string
}

Suscripciones: el concepto

Un tipo raíz Subscription permite que los clientes reciban un flujo de resultados cuando ocurren eventos (un mensaje nuevo, una variación del precio). La especificación de GraphQL define la semántica de las suscripciones, pero graphql-php ejecuta una sola operación por llamada; no ejecuta por sí mismo un servidor de sockets de larga duración. Usted debe proporcionar el transporte.

  • graphql-php resuelve la carga útil de la suscripción para cada evento que usted envía.
  • Un transporte (WebSocket mediante Ratchet/Mercure/Pusher) entrega los eventos a los clientes.

Estructura de un resolver de suscripción

En la práctica, una suscripción se divide en un paso de subscribe (registra el interés y devuelve una fuente de eventos) y un paso de resolve (asigna cada evento a la carga útil del campo). Muchas pilas PHP combinan graphql-php con Mercure o con un broker de pub/sub; el resolver siguiente muestra la asignación por evento de la que se encarga 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'],
        ],
    ],
]);

El contexto es su canal de autenticación e inyección de dependencias

El tercer argumento del resolver, $context, se crea una vez por solicitud y se pasa a todos los resolvers. Es el lugar adecuado para el usuario autenticado, una conexión a la base de datos y sus DataLoaders. Centralizar aquí la autenticación mantiene los resolvers ligeros: consultan al contexto para saber quién es el usuario en lugar de deducirlo de nuevo.

<?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']);
};

Comprobación rápida

¿Cómo hace visible un mensaje de excepción para los clientes de GraphQL?

Resumen

Ha aprendido el núcleo de la ejecución de GraphQL:

  • Los resolvers reciben ($value, $args, $context, $info); el resolver predeterminado lee las claves y los getters del padre.
  • La resolución se propaga del padre al hijo, que es el origen de N+1.
  • Devolver Deferred/promesas permite agrupar consultas.
  • Las mutaciones son campos raíz secuenciales que usan InputObjectType; realice la autenticación y la validación en el resolver.
  • Las suscripciones definen la resolución de la carga útil, mientras que usted proporciona el transporte.
  • ClientAware controla qué mensajes de error pueden ver los clientes.

Siguiente paso: eliminar el problema N+1 con DataLoader.

Preguntas frecuentes

¿La lección «Resolvers, mutaciones y suscripciones» es gratis?

Sí — el texto completo de «Resolvers, mutaciones y suscripciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de PHP Academy, actualiza a CoddyKit PRO. El curso de PHP Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Resolvers, mutaciones y suscripciones»?

Obtenga y modifique datos mediante resolvers Practicas PHP Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar PHP Academy?

No se requiere experiencia previa. PHP Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Resolvers, mutaciones y suscripciones»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de PHP Academy?

Sí. Cada lección de PHP Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. GraphQL frente a REST
  2. Creación de un esquema con graphql-php
  3. Resolvers, mutaciones y suscripciones
  4. Rendimiento: N+1 y DataLoader
← Volver a PHP Academy