0Pricing
PHP Academy · Lekcja

Resolvery, mutacje i subskrypcje

Pobieraj i zmieniaj dane za pomocą resolverów

Resolvery, mutacje i subskrypcje to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 3 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.

Resolvery zawierają logikę

Schemat opisuje, co istnieje, a resolvery decydują, jak jest tworzona wartość każdego pola. Resolver to po prostu funkcja wywoływalna. Mutacje są resolverami, które zmieniają stan. Subskrypcje przesyłają wartości w czasie. Ta lekcja omawia wszystkie trzy elementy oraz model wykonywania, który je łączy.

Sygnatura resolvera

Każdy resolver otrzymuje cztery argumenty: ($objectValue, $args, $context, ResolveInfo $info).

  • $objectValue — rozwiązana wartość rodzica (na najwyższym poziomie jest to rootValue).
  • $args — argumenty pola.
  • $context — współdzielony stan dla pojedynczego żądania (uchwyt do bazy danych, bieżący użytkownik).
  • $info — metadane AST i pola (nazwa pola, zbiór selekcji, ścieżka).
<?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']);
};

Domyślny resolver

Jeśli nie zostanie podane resolve, domyślny resolver graphql-php odczytuje nazwę pola z wartości nadrzędnej: jako klucz tablicy, publiczną właściwość albo metodę get<Field>(). Oznacza to, że często można rozwiązać całe typy obiektowe bez dodatkowego kodu, zwracając z rodzica zwykłe tablice lub obiekty 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']);

Resolvery kaskadowo przechodzą od rodzica do dziecka

Wykonywanie przebiega z góry na dół: resolver Query.user zwraca użytkownika, który staje się wartością $objectValue dla User.posts, a wynik tego pola staje się rodzicem dla każdego Post.title. Zrozumienie tej kaskady jest kluczowe — właśnie w tym miejscu pojawia się problem N+1, omówiony w następnej lekcji.

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

Zwracanie obietnic (asynchronicznie)

Resolvery mogą zwracać wartość albo obietnicę. graphql-php udostępnia synchroniczny adapter obietnic; z adapterami ReactPHP/Amp rozwiązywanie może być odroczone i grupowane. Nawet w trybie synchronicznym zwracanie obiektów Deferred pozwala executorowi zebrać zadania i wykonać je po zakończeniu bieżącego poziomu rozwiązywania — na tym mechanizmie opiera się 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']);
    });
};

Mutacje zmieniają stan

Mutation to po prostu typ główny o nazwie Mutation. Zgodnie z konwencją jego pola najwyższego poziomu są wykonywane sekwencyjnie, a nie równolegle, dzięki czemu kolejność efektów ubocznych jest zachowana. Dane wejściowe zazwyczaj grupuje się w InputObjectType, aby uzyskać przejrzystą sygnaturę.

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

Konfigurowanie typu Mutation

Pole mutacji przyjmuje obiekt wejściowy jako argument i zwraca utworzoną encję, aby klienci mogli odczytać jej pola w ramach tego samego żądania. Walidację i autoryzację należy wykonywać wewnątrz resolvera, zgłaszając wyjątek w razie niepowodzenia.

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

Błędy: bezpieczne dla klienta i wewnętrzne

Domyślnie graphql-php ukrywa komunikaty wyjątków i wyświetla Internal server error, aby nie ujawniać szczegółów wewnętrznych. Aby udostępnić klientom komunikat, należy zaimplementować GraphQL\Error\ClientAware i zwracać true z metody isClientSafe(). Kody możliwe do odczytania maszynowego można dodać za pomocą extensions.

<?php
use GraphQL\Error\ClientAware;

class ValidationError extends \RuntimeException implements ClientAware {
    public function isClientSafe(): bool { return true; }
    // older versions also used getCategory(): string
}

Subskrypcje: koncepcja

Główny typ Subscription pozwala klientom otrzymywać strumień wyników, gdy wystąpią zdarzenia (nowa wiadomość, zmiana ceny). Specyfikacja GraphQL definiuje semantykę subskrypcji, ale graphql-php wykonuje jedną operację na wywołanie — samo nie uruchamia długotrwałego serwera gniazd. Transport należy zapewnić samodzielnie.

  • graphql-php rozwiązuje payload subskrypcji dla każdego wysłanego zdarzenia.
  • Transport (WebSocket przez Ratchet/Mercure/Pusher) dostarcza zdarzenia klientom.

Struktura resolvera subskrypcji

W praktyce subskrypcję dzieli się na etap subscribe (rejestruje zainteresowanie i zwraca źródło zdarzeń) oraz etap resolve (mapuje każde zdarzenie na payload pola). Wiele stosów PHP łączy graphql-php z Mercure lub brokerem pub/sub; poniższy resolver pokazuje mapowanie poszczególnych zdarzeń, za które odpowiada 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 to kanał autoryzacji i DI

Trzeci argument resolvera, $context, jest tworzony raz na żądanie i przekazywany do każdego resolvera. To właściwe miejsce na uwierzytelnionego użytkownika, połączenie z bazą danych i moduły DataLoader. Centralizacja autoryzacji pozwala zachować cienkie resolvery — pytają one context o tożsamość użytkownika, zamiast ustalać ją ponownie.

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

Szybkie sprawdzenie

Jak sprawić, aby komunikat wyjątku był widoczny dla klientów GraphQL?

Podsumowanie

Poznano najważniejsze elementy wykonywania GraphQL:

  • Resolvery przyjmują ($value, $args, $context, $info), a domyślny resolver odczytuje klucze lub gettery z wartości nadrzędnej.
  • Rozwiązywanie przebiega kaskadowo od rodzica do dziecka — stąd bierze się problem N+1.
  • Zwracanie Deferred lub obietnic umożliwia grupowanie operacji.
  • Mutacje to sekwencyjne pola główne korzystające z InputObjectType; autoryzację i walidację należy wykonywać w resolverze.
  • Subskrypcje definiują rozwiązywanie payloadu, natomiast transport należy zapewnić samodzielnie.
  • ClientAware kontroluje, które komunikaty o błędach mogą zobaczyć klienci.

Następnie: rozwiązanie problemu N+1 za pomocą DataLoader.

Często zadawane pytania

Czy lekcja „Resolvery, mutacje i subskrypcje” jest bezpłatna?

Tak — pełny tekst „Resolvery, mutacje i subskrypcje” 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 „Resolvery, mutacje i subskrypcje”?

Pobieraj i zmieniaj dane za pomocą resolverów Ć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 3 z 4.

Ile czasu zajmuje lekcja „Resolvery, mutacje i subskrypcje”?

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