リゾルバー、ミューテーション、サブスクリプション
リゾルバーを通じてデータを取得・変更します。
「リゾルバー、ミューテーション、サブスクリプション」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。
リゾルバーがロジックを担う
スキーマは存在するものが何かを記述し、リゾルバーは各フィールドの値をどのように生成するかを決定します。リゾルバーは単なるcallableです。Mutationは状態を変更するリゾルバーです。Subscriptionは時間の経過に伴って値をストリーム配信します。このレッスンでは、これら3つすべてと、それらを結び付ける実行モデルを扱います。
リゾルバーのシグネチャ
すべてのリゾルバーは4つの引数を受け取ります。($objectValue, $args, $context, ResolveInfo $info)です。
- $objectValue — 親の解決済みの値(最上位では
rootValue)。 - $args — フィールドの引数。
- $context — リクエストごとに共有される状態(DBハンドル、現在のユーザーなど)。
- $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のデフォルトリゾルバーは親の値からフィールド名を読み取ります。対象となるのは、配列キー、publicプロパティ、またはget<Field>()メソッドです。そのため、親から単純な配列やDTOを返すだけで、ボイラープレートコードなしにオブジェクト型全体を解決できることがよくあります。
<?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']),
],
],
]);
Promiseを返す(非同期)
リゾルバーは値またはPromiseを返せます。graphql-phpには同期Promiseアダプターが付属しており、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は状態を変更する
Mutationは、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(),
],
]);
Mutation型を接続する
Mutationフィールドは入力オブジェクトを引数として受け取り、作成されたエンティティを返します。これにより、クライアントは同じラウンドトリップでフィールドを読み戻せます。検証と認可はリゾルバー内で行い、失敗した場合は例外をスローします。
<?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
}
Subscription:概念
Subscriptionルート型を使うと、クライアントはイベントが発生したとき(新しいメッセージや価格の更新など)に結果のストリームを受け取れます。GraphQL仕様ではSubscriptionのセマンティクスが定義されていますが、graphql-phpは呼び出しごとに1つの操作を実行するだけで、それ自体が長時間稼働するソケットサーバーになるわけではありません。通信方式は自分で用意します。
- graphql-phpは、プッシュした各イベントのペイロードに対してSubscriptionを解決します。
- 通信方式(Ratchet/Mercure/Pusherを介したWebSocket)がクライアントにイベントを配信します。
Subscriptionリゾルバーの構成
実際には、Subscriptionをsubscribeステップ(購読を登録し、イベントソースを返す)とresolveステップ(各イベントをフィールドのペイロードに変換する)に分けます。多くのPHPスタックでは、graphql-phpとMercureまたはpub/subブローカーを組み合わせます。次のリゾルバーは、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'],
],
],
]);
Contextは認証とDIの受け渡し役
リゾルバーの第3引数である$contextは、リクエストごとに1回構築され、すべてのリゾルバーに渡されます。認証済みユーザー、データベース接続、DataLoaderを置くのに適した場所です。ここに認証処理を集約すると、リゾルバーは簡潔になります。リゾルバーはユーザーが誰かをContextに尋ねればよく、自分で再計算する必要がないためです。
<?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/Promiseを返すことで、バッチ処理が可能になります。- Mutationは
InputObjectTypeを使う順次実行のルートフィールドです。認証と検証はリゾルバーで行います。 - Subscriptionではペイロードの解決方法を定義し、通信方式は自分で用意します。
ClientAwareによって、クライアントに表示できるエラーメッセージを制御します。
次は、DataLoaderでN+1問題を解消します。
よくある質問
「リゾルバー、ミューテーション、サブスクリプション」レッスンは無料ですか?
はい。「リゾルバー、ミューテーション、サブスクリプション」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。
「リゾルバー、ミューテーション、サブスクリプション」で何を学びますか?
リゾルバーを通じてデータを取得・変更します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
PHP Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「リゾルバー、ミューテーション、サブスクリプション」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPHP Academyレッスンでコードを書いて実行できますか?
はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- GraphQLとRESTの比較
- graphql-phpでスキーマを構築する
- リゾルバー、ミューテーション、サブスクリプション
- パフォーマンス:N+1とDataLoader