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
Deferredlub 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.
ClientAwarekontroluje, 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
- GraphQL a REST
- Tworzenie schematu za pomocą graphql-php
- Resolvery, mutacje i subskrypcje
- Wydajność: N+1 i DataLoader