리졸버, 변이와 구독
리졸버를 통해 데이터를 가져오고 변경합니다.
리졸버, 변이와 구독은(는) 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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.