0Pricing
PHP Academy · Lekcja

Tworzenie schematu za pomocą graphql-php

Definiuj typy i schemat za pomocą webonyx/graphql-php

Tworzenie schematu za pomocą graphql-php to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.

graphql-php: implementacja referencyjna

webonyx/graphql-php to de facto port referencyjnej implementacji GraphQL dla PHP. Udostępnia system typów, parser, walidator i mechanizm wykonywania zapytań. Schemat można zdefiniować programowo (za pomocą obiektów PHP) albo na podstawie SDL (schema-first). W tej lekcji zbudujemy schemat ręcznie, aby dokładnie zrozumieć działanie każdego jego elementu.

composer require webonyx/graphql-php

Skalary i rejestr typów

Każda wartość GraphQL ostatecznie sprowadza się do skalaru: Int, Float, String, Boolean, ID. W graphql-php te typy są dostępne za pośrednictwem fasady Type. Ponieważ typy obiektowe odwołują się do siebie nawzajem, a nawet do samych siebie, często stosuje się statyczny TypeRegistry, który zapamiętuje każdy typ, dzięki czemu jest on tworzony tylko raz.

<?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!"

Definiowanie typu ObjectType

Obiekt ObjectType ma właściwości name i mapę fields. Każde pole deklaruje swój type i opcjonalnie funkcję zwrotną resolve. Jeśli pominie się resolve, graphql-php użyje domyślnego resolvera, który odczytuje odpowiedni klucz tablicy, właściwość lub getter z wartości nadrzędnej.

<?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 i opakowywanie typów listowych

Typy opakowujące wyrażają możliwość wystąpienia wartości null oraz liczność:

  • Type::nonNull(T) → T! (wartość nigdy nie jest null).
  • Type::listOf(T) → [T] (lista, która może być null i może zawierać elementy null).
  • [Post!]! = lista, która nie jest null i zawiera posty, które również nie są null → nonNull(listOf(nonNull($postType))).

Należy ustawić to poprawnie: jest to kontrakt schematu dotyczący wartości null przekazywanych klientom.

<?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!]!

Leniwe pola rozwiązują problem odwołań cyklicznych

User ma pole posts, a Post ma pole author (typu User). Aby zbudować wzajemnie odwołujące się typy, należy przekazać fields jako closure, a nie tablicę. Closure zostanie wykonane z opóźnieniem, gdy oba typy będą już istnieć, co pozwala uniknąć problemu jajka i kury.

<?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
            ],
        ]);
    }
}

Argumenty pól

Pola mogą przyjmować argumenty. Należy zadeklarować je pod kluczem args; trafią one jako drugi parametr ($args) resolvera. Argumenty również mają typy, mogą być non-null i mogą mieć wartość 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']})",
];

Główny typ Query

Każdy schemat potrzebuje głównego typu Query — punktów wejścia, od których klienci mogą rozpocząć zapytanie. Tutaj udostępnimy jedno pole hello, aby móc wykonać zapytanie od początku do końca. Pierwszym argumentem resolvera głównego jest rootValue schematu (często 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'],
        ],
    ],
]);

Składanie i wykonywanie schematu

Należy opakować typ zapytania w Schema i przekazać ciąg zapytania do GraphQL::executeQuery(). Obiekt wyniku można przekształcić w kanoniczną tablicę { data, errors } za pomocą 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"}}

Alternatywa schema-first z BuildSchema

Jeśli preferują Państwo SDL, BuildSchema::build() analizuje ciąg schematu i tworzy schemat wykonywalny. Następnie resolvery dołącza się osobno (np. za pomocą funkcji zwrotnej resolvera pola), zachowując deklaratywne definicje typów i umieszczając logikę w 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.

Walidacja przed wdrożeniem

graphql-php automatycznie sprawdza przychodzące zapytania względem schematu przed ich wykonaniem. Można również sprawdzić wewnętrzną spójność samego schematu podczas jego budowania lub w CI za pomocą $schema->assertValid() — wykrywać literówki i uszkodzone odwołania przed wdrożeniem, a nie dopiero podczas obsługi żądania.

<?php
use GraphQL\Type\Schema;

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

Enumy i niestandardowe skalary

Poza obiektami większość schematów uzupełniają jeszcze dwa rodzaje typów. EnumType ogranicza pole do ustalonego zbioru nazwanych wartości. CustomScalarType pozwala definiować skalary domenowe (DateTime, Email) za pomocą własnej logiki serialize/parseValue/parseLiteral, dzięki czemu wartości są sprawdzane i normalizowane na granicy systemu.

<?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.

Szybkie sprawdzenie

Dlaczego należy przekazywać fields jako closure?

Podsumowanie

Zbudowano schemat od podstaw:

  • Skalary i typy opakowujące (nonNull, listOf) wyrażają kontrakt dotyczący wartości null i liczności.
  • ObjectType z mapą pól lub leniwym closure definiuje struktury danych.
  • Rejestr typów zapamiętujący utworzone typy obsługuje odwołania cykliczne.
  • Pola przyjmują typowane args, a główny typ Query jest punktem wejścia.
  • GraphQL::executeQuery() wykonuje zapytania, a assertValid() chroni schemat w CI.

Następnie: resolvery, mutacje i subskrypcje.

Często zadawane pytania

Czy lekcja „Tworzenie schematu za pomocą graphql-php” jest bezpłatna?

Tak — pełny tekst „Tworzenie schematu za pomocą graphql-php” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Tworzenie schematu za pomocą graphql-php”?

Definiuj typy i schemat za pomocą webonyx/graphql-php Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć PHP Academy?

Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Tworzenie schematu za pomocą graphql-php”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?

Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. GraphQL a REST
  2. Tworzenie schematu za pomocą graphql-php
  3. Resolvery, mutacje i subskrypcje
  4. Wydajność: N+1 i DataLoader
← Powrót do PHP Academy