0Pricing
PHP Academy · 课时

使用 graphql-php 构建架构

使用 webonyx/graphql-php 定义类型和架构

使用 graphql-php 构建架构 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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(),
    ],
]);

非空与列表包装

包装类型用于表达可空性和数量约束:

  • Type::nonNull(T) → T!(永不为空)。
  • Type::listOf(T) → [T](一个可能为空、且成员也可能为空的列表)。
  • [Post!]! = 一个非空元素组成的非空帖子列表 → nonNull(listOf(nonNull($postType)))。

请务必正确设置:这就是您的模式与客户端之间关于空值的契约。

<?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 包含 author(一个 User)。要构建相互引用的类型,请将 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 键下声明它们;它们会作为解析器的第二个参数($args)传入。参数本身也有类型,可以是非空的,还可以携带 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']})",
];

根查询类型

每个模式都需要一个根 查询 类型——客户端可以从这里开始访问。这里我们公开一个 hello 字段,以便端到端执行查询。根解析器的第一个参数是模式的 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'],
        ],
    ],
]);

组装并执行模式

将查询类型包装在 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() 验证模式本身在内部是否一致——在部署前而不是请求期间捕获拼写错误和断开的引用。

<?php
use GraphQL\Type\Schema;

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

枚举与自定义标量

除了对象之外,还有两种类型可以补足大多数模式。EnumType 将字段限制为一组固定的命名值。CustomScalarType 允许您定义领域标量(DateTime、电子邮件),并使用自定义的 serialize/parseValue/parseLiteral 逻辑,从而在边界处验证和规范化值。

<?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)表达空值/数量契约。
  • 带有字段映射(或惰性闭包)的 ObjectType 定义数据形状。
  • 具有记忆化缓存功能的类型注册表处理循环引用。
  • 字段接受带类型的 args;根 Query 类型是入口。
  • GraphQL::executeQuery() 执行查询;assertValid() 在持续集成中保护模式。

下一步:解析器、变更和订阅。

常见问题解答

「使用 graphql-php 构建架构」课时是免费的吗?

是的 — 「使用 graphql-php 构建架构」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。

「使用 graphql-php 构建架构」这节课中我会学到什么?

使用 webonyx/graphql-php 定义类型和架构 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 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