0Pricing
PHP Academy · 강의

리졸버, 변이와 구독

리졸버를 통해 데이터를 가져오고 변경합니다.

리졸버, 변이와 구독은(는) CoddyKit의 무료 PHP Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PHP Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

해결기에 로직이 들어갑니다

스키마는 무엇이 존재하는지 설명하고, 해결기는 각 필드의 값이 어떻게 생성되는지 결정합니다. 해결기는 호출 가능한 함수일 뿐입니다. 변경 작업은 상태를 변경하는 해결기입니다. 구독은 시간에 따라 값을 스트리밍합니다. 이 레슨에서는 세 가지와 이를 연결하는 실행 모델을 모두 다룹니다.

해결기 시그니처

모든 해결기는 네 개의 인수를 받습니다: ($objectValue, $args, $context, ResolveInfo $info).

  • $objectValue — 부모의 해결된 값입니다(최상위에서는 rootValue).
  • $args — 필드의 인수입니다.
  • $context — 요청마다 공유되는 상태입니다(데이터베이스 핸들, 현재 사용자).
  • $info — AST/필드 메타데이터입니다(필드 이름, 선택 집합, 경로).
<?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']);
};

기본 해결기

resolve를 제공하지 않으면 graphql-php의 기본 해결기는 부모 값에서 필드 이름을 읽습니다. 배열 키, 공개 속성 또는 get<Field>() 메서드를 사용합니다. 따라서 부모에서 일반 배열이나 데이터 전송 객체를 반환하기만 해도 상용구 코드 없이 전체 객체 타입을 해결할 수 있는 경우가 많습니다.

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

해결기는 부모에서 자식으로 연쇄됩니다

실행은 위에서 아래로 진행됩니다. Query.user 해결기가 사용자를 반환하면 그 값이 User.posts의 $objectValue가 되고, 그 결과가 각 Post.title의 부모가 됩니다. 이 연쇄를 이해하는 것은 필수적입니다 — 바로 이 지점에서 N+1 문제가 나타납니다(다음 레슨에서 다룹니다).

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

프로미스 반환(비동기)

해결기는 값 또는 프로미스를 반환할 수 있습니다. graphql-php는 동기 프로미스 어댑터를 제공합니다. ReactPHP/Amp 어댑터를 사용하면 해결을 지연하고 일괄 처리할 수 있습니다. 동기 방식에서도 Deferred 객체를 반환하면 실행기가 작업을 모아 현재 해결 단계가 끝난 뒤 실행할 수 있습니다 — 이것이 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']);
    });
};

변경 작업은 상태를 변경합니다

변경 작업은 이름이 Mutation인 루트 타입일 뿐입니다. 관례상 최상위 필드는 부작용 순서를 보장하기 위해 병렬이 아니라 순차적으로 실행됩니다. 입력값은 일반적으로 깔끔한 시그니처를 위해 InputObjectType으로 묶습니다.

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

변경 작업 타입 연결하기

변경 작업 필드는 입력 객체를 인수로 받고 생성된 엔터티를 반환합니다(클라이언트가 같은 왕복에서 필드를 다시 읽을 수 있도록 하기 위해서입니다). 검증과 권한 부여를 해결기 내부에서 수행하고, 실패하면 예외를 던지십시오.

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

오류: 클라이언트에 안전한 오류와 내부 오류

graphql-php는 기본적으로 예외 메시지를 숨기고 내부 정보 노출을 막기 위해 Internal server error를 표시합니다. 클라이언트에 메시지를 표시하려면 GraphQL\Error\ClientAware를 구현하고 isClientSafe()에서 true를 반환하십시오. extensions를 통해 기계가 읽을 수 있는 코드를 추가하십시오.

<?php
use GraphQL\Error\ClientAware;

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

구독: 개념

구독 루트 타입을 사용하면 이벤트가 발생할 때(새 메시지, 가격 변동) 클라이언트가 결과 스트림을 받을 수 있습니다. GraphQL 사양은 구독의 의미 체계를 정의하지만, graphql-php는 호출마다 단일 작업을 실행할 뿐이며 자체적으로 장시간 실행되는 소켓 서버를 구동하지는 않습니다. 전송 계층은 직접 제공해야 합니다.

  • graphql-php는 사용자가 전달하는 각 이벤트에 대해 구독 페이로드를 해결합니다.
  • 전송 계층( Ratchet/Mercure/Pusher를 통한 WebSocket)이 이벤트를 클라이언트에 전달합니다.

구독 해결기의 형태

실제로는 구독을 subscribe 단계(관심을 등록하고 이벤트 소스를 반환)와 resolve 단계(각 이벤트를 필드의 페이로드로 매핑)로 나눕니다. 많은 PHP 스택이 graphql-php를 Mercure 또는 게시/구독 브로커와 함께 사용합니다. 아래 해결기는 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'],
        ],
    ],
]);

컨텍스트는 인증과 DI 채널입니다

세 번째 해결기 인수인 $context는 요청마다 한 번 생성되어 모든 해결기에 전달됩니다. 인증된 사용자, 데이터베이스 연결, DataLoaders를 여기에 넣는 것이 적절합니다. 여기서 인증을 중앙화하면 해결기가 간결해집니다 — 해결기는 인증 정보를 다시 계산하지 않고 컨텍스트에 사용자가 누구인지 묻기만 하면 됩니다.

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

간단 확인

예외 메시지를 GraphQL 클라이언트에 표시하려면 어떻게 해야 합니까?

복습

GraphQL 실행의 핵심을 배웠습니다:

  • 해결기는 ($value, $args, $context, $info)를 받고, 기본 해결기는 부모에서 키/게터를 읽습니다.
  • 해결은 부모에서 자식으로 연쇄되며, 이것이 N+1의 원인입니다.
  • Deferred/프로미스를 반환하면 일괄 처리가 가능합니다.
  • 변경 작업은 InputObjectType을 사용하는 순차적 루트 필드이며, 해결기에서 인증/검증을 수행해야 합니다.
  • 구독은 페이로드 해결을 정의하고, 전송 계층은 직접 제공해야 합니다.
  • ClientAware가 클라이언트에 표시할 수 있는 오류 메시지를 제어합니다.

다음: DataLoader로 N+1 문제 해결하기.

자주 묻는 질문

“리졸버, 변이와 구독” 강의는 무료인가요?

네 — “리졸버, 변이와 구독” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PHP Academy 강의 전체를 잠금 해제할 수 있습니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“리졸버, 변이와 구독”에서 뭘 배우나요?

리졸버를 통해 데이터를 가져오고 변경합니다. 브라우저에서 직접 실행하는 실습 코드로 PHP Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

PHP Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 PHP Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“리졸버, 변이와 구독” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 PHP Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 PHP Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. GraphQL과 REST 비교
  2. graphql-php로 스키마 구축
  3. 리졸버, 변이와 구독
  4. 성능: N+1과 DataLoader
← PHP Academy(으)로 돌아가기