0Pricing
PHP Academy · レッスン

graphql-phpでスキーマを構築する

webonyx/graphql-phpで型とスキーマを定義します。

「graphql-phpでスキーマを構築する」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。

graphql-php:リファレンス実装

webonyx/graphql-phpは、事実上標準となっているGraphQLリファレンス実装のPHP移植版です。型システム、パーサー、バリデーター、エグゼキューターを提供します。スキーマはプログラムで(PHPオブジェクトを使って)定義することも、SDLから(スキーマファーストで)定義することもできます。このレッスンでは、各要素の役割を正確に理解できるよう、スキーマを手作業で構築します。

composer require webonyx/graphql-php

スカラーと型レジストリ

GraphQLのすべての値は、最終的にスカラーになります。Int、Float、String、Boolean、IDが該当します。graphql-phpでは、これらはTypeファサード上にあります。オブジェクト型は互いに(場合によっては自分自身も)参照するため、各型を一度だけ構築できるよう、静的なTypeRegistryでメモ化するのが一般的なパターンです。

<?php
use GraphQL\Type\Definition\Type;

// Built-in scalars, returned as singletons:
var_dump(Type::int()->name);     // "Int"
var_dump(Type::string()->name);  // "String"
var_dump(Type::id()->name);      // "ID"
var_dump(Type::nonNull(Type::string())->toString()); // "String!"

ObjectTypeを定義する

ObjectTypeにはnameとfieldsマップがあります。各フィールドではtypeを宣言し、必要に応じてresolveコールバックを指定します。resolveを省略すると、graphql-phpはデフォルトリゾルバーを使用し、親の値から対応する配列キー、プロパティ、またはゲッターを読み取ります。

<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

$postType = new ObjectType([
    'name' => 'Post',
    'fields' => [
        'id'    => Type::nonNull(Type::id()),
        'title' => Type::nonNull(Type::string()),
        'body'  => Type::string(),
    ],
]);

Non-Nullとリストによるラップ

ラップ型によって、null許容性と要素数を表現できます。

  • Type::nonNull(T) → T!(nullにならない)。
  • Type::listOf(T) → [T](リスト。リスト自体がnullになる可能性があり、nullの要素を含む可能性もあります)。
  • [Post!]! = nullでないPostの、nullでないリスト → nonNull(listOf(nonNull($postType)))。

ここは正しく定義してください。これは、クライアントに対するスキーマのnull契約です。

<?php
use GraphQL\Type\Definition\Type;

// [Post!]!  -- a required list whose elements are never null
$wrapped = Type::nonNull(Type::listOf(Type::nonNull(Type::string())));
echo $wrapped->toString(), "\n"; // [String!]!

遅延フィールドで循環参照を解消する

Userにはpostsがあり、Postには(Userである)authorがあります。相互参照する型を構築するには、fieldsを配列ではなくクロージャとして渡します。クロージャは両方の型が存在した後に遅延実行されるため、卵が先か鶏が先かという問題を回避できます。

<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

class Types {
    private static array $cache = [];

    public static function user(): ObjectType {
        return self::$cache['User'] ??= new ObjectType([
            'name' => 'User',
            'fields' => fn() => [           // lazy!
                'id'    => Type::nonNull(Type::id()),
                'name'  => Type::nonNull(Type::string()),
                'posts' => Type::listOf(self::post()),
            ],
        ]);
    }

    public static function post(): ObjectType {
        return self::$cache['Post'] ??= new ObjectType([
            'name' => 'Post',
            'fields' => fn() => [
                'id'     => Type::nonNull(Type::id()),
                'title'  => Type::nonNull(Type::string()),
                'author' => self::user(),    // back-reference
            ],
        ]);
    }
}

フィールド引数

フィールドには引数を渡せます。argsキーの下で宣言すると、リゾルバーの第2引数($args)として渡されます。引数自体にも型があり、Non-Nullにでき、defaultValueを設定することもできます。

<?php
use GraphQL\Type\Definition\Type;

$userField = [
    'type' => Type::string(),
    'args' => [
        'id'     => Type::nonNull(Type::id()),
        'locale' => ['type' => Type::string(), 'defaultValue' => 'en'],
    ],
    'resolve' => fn($root, array $args) => "user {$args['id']} ({$args['locale']})",
];

ルートQuery型

すべてのスキーマにはルートQuery型が必要です。これは、クライアントが処理を開始できるエントリーポイントです。ここでは、最初から最後まで実行できることを確認するため、単一のhelloフィールドを公開します。ルートリゾルバーの第1引数は、スキーマのrootValue(多くの場合null)です。

<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

$queryType = new ObjectType([
    'name' => 'Query',
    'fields' => [
        'hello' => [
            'type' => Type::string(),
            'args' => ['name' => Type::nonNull(Type::string())],
            'resolve' => fn($root, array $args) => 'Hello, ' . $args['name'],
        ],
    ],
]);

スキーマの組み立てと実行

Query型をSchemaでラップし、クエリ文字列をGraphQL::executeQuery()に渡して実行します。結果オブジェクトは、toArray()によって標準的な{ data, errors }配列に変換できます。

<?php
require 'vendor/autoload.php';

use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;

$queryType = new ObjectType([
    'name' => 'Query',
    'fields' => [
        'hello' => [
            'type' => Type::string(),
            'args' => ['name' => Type::nonNull(Type::string())],
            'resolve' => fn($root, $args) => 'Hello, ' . $args['name'],
        ],
    ],
]);

$schema = new Schema(['query' => $queryType]);
$result = GraphQL::executeQuery($schema, '{ hello(name: "Ada") }');
echo json_encode($result->toArray());
// {"data":{"hello":"Hello, Ada"}}

BuildSchemaを使うスキーマファーストの方法

SDLを好む場合は、BuildSchema::build()によってスキーマ文字列を解析し、実行可能なスキーマを構築できます。その後、リゾルバー(フィールドリゾルバーのコールバックなど)を別途設定します。これにより、型定義は宣言的に保ちながら、ロジックはPHPに置けます。

<?php
use GraphQL\Utils\BuildSchema;

$sdl = <<<'GQL'
type Query {
  hello(name: String!): String
}
GQL;

$schema = BuildSchema::build($sdl);
// Provide resolvers via the executeQuery $fieldResolver argument
// or with a type config decorator.

リリース前に検証する

graphql-phpは、実行前に受け取ったクエリをスキーマに対して自動的に検証します。また、$schema->assertValid()を使って、構築時やCI実行時にスキーマ自体の整合性を検証することもできます。デプロイ後のリクエスト時ではなく、デプロイ前に誤記や壊れた参照を検出しましょう。

<?php
use GraphQL\Type\Schema;

/** @var Schema $schema */
$schema->assertValid(); // throws InvariantViolation on a broken schema
echo "schema OK\n";

列挙型とカスタムスカラー

オブジェクト以外にも、ほとんどのスキーマを構成する2種類の型があります。EnumTypeは、フィールドの値を固定された名前付きの値の集合に制限します。CustomScalarTypeを使うと、独自のserialize/parseValue/parseLiteralロジックによってドメインスカラー(DateTime、Email)を定義できます。これにより、値を境界で検証し、正規化できます。

<?php
use GraphQL\Type\Definition\EnumType;

$statusEnum = new EnumType([
    'name' => 'PostStatus',
    'values' => [
        'DRAFT'     => ['value' => 0],
        'PUBLISHED' => ['value' => 1],
        'ARCHIVED'  => ['value' => 2],
    ],
]);
// A field typed as $statusEnum only accepts DRAFT/PUBLISHED/ARCHIVED.

理解度チェック

fieldsをクロージャとして渡すのはなぜですか?

まとめ

スキーマをゼロから構築しました。

  • スカラーとラップ型(nonNull、listOf)によって、null許容性と要素数の契約を表現します。
  • フィールドマップ(または遅延クロージャ)を持つObjectTypeによって、データの形を定義します。
  • メモ化する型レジストリによって、循環参照を処理します。
  • フィールドには型付きのargsを渡し、ルートQuery型をエントリーポイントにします。
  • GraphQL::executeQuery()で実行し、assertValid()でCIにおけるスキーマの整合性を守ります。

次は、リゾルバー、Mutation、Subscriptionです。

よくある質問

「graphql-phpでスキーマを構築する」レッスンは無料ですか?

はい。「graphql-phpでスキーマを構築する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。

「graphql-phpでスキーマを構築する」で何を学びますか?

webonyx/graphql-phpで型とスキーマを定義します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

PHP Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「graphql-phpでスキーマを構築する」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPHP Academyレッスンでコードを書いて実行できますか?

はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

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

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