0Pricing
PHP Academy · レッスン

リゾルバー、ミューテーション、サブスクリプション

リゾルバーを通じてデータを取得・変更します。

「リゾルバー、ミューテーション、サブスクリプション」は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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. GraphQLとRESTの比較
  2. graphql-phpでスキーマを構築する
  3. リゾルバー、ミューテーション、サブスクリプション
  4. パフォーマンス:N+1とDataLoader
← PHP Academyに戻る