PHP Academy · Урок

Создание схемы с graphql-php

Определяйте типы и схему с помощью webonyx/graphql-php

Урок 2 из 413 шагов

«Создание схемы с graphql-php» — бесплатный урок PHP Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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']})",
];

Корневой тип Query

Каждой схеме нужен корневой тип Query — точки входа, с которых клиенты начинают запросы. Здесь мы предоставляем единственное поле 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(). Объект результата преобразуется в канонический массив { data, errors } с помощью toArray().

<?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 автоматически проверяет входящие запросы по схеме перед выполнением. Вы также можете проверить внутреннюю согласованность самой схемы во время сборки или в CI с помощью $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() защищает схему в CI.

Далее: резолверы, мутации и подписки.

Можно начать бесплатно

Изучай PHP с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
49
Уроки
195

Часто задаваемые вопросы

Урок «Создание схемы с graphql-php» бесплатный?

Да — полный текст урока «Создание схемы с graphql-php» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Создание схемы с graphql-php»?

Определяйте типы и схему с помощью webonyx/graphql-php Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 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