IFTTT za pomocą dyrektyw
Gato GraphQL oferuje możliwość implementowania strategii IFTTT (If This Then That, „jeśli to, to tamto") za pomocą dyrektyw. Dyrektywy te są dynamicznie dodawane do zapytania, gdy w zapytaniu pojawi się określone pole lub dyrektywa.
Ogólnie rzecz biorąc, IFTTT to reguły, które wyzwalają akcje za każdym razem, gdy nastąpi określone zdarzenie. W naszym przypadku pary zdarzenie/akcja wyglądają następująco:
- Jeśli „pole X zostanie znalezione w zapytaniu", to „dołącz dyrektywę Y do pola X"
- Jeśli „dyrektywa Z zostanie znaleziona w zapytaniu", to „wykonaj dyrektywę Y przed/po dyrektywie Z"
Dynamiczne dodawanie dyrektyw IFTTT do schematu jest procesem rekurencyjnym: taka dyrektywa może sama w sobie mieć skonfigurowany własny zestaw dyrektyw IFTTT, które również są dodawane do łańcucha dyrektyw.
Gdzie jest stosowane
Za kulisami klienci Gato GraphQL używają tego mechanizmu do konfigurowania schematu GraphQL.
Na przykład Access Control umożliwia wybór reguł kontroli dostępu stosowanych do operacji, pól i dyrektyw. To właśnie dzięki IFTTT reguły te są stosowane do tych elementów schematu GraphQL.

Ogólnie rzecz biorąc, oto kilka przykładowych przypadków użycia:
Definiowanie max-age cache control pole po polu
Dołączenie dyrektywy @CacheControl do wszystkich pól z dostosowaniem wartości parametru maxAge: 1 rok dla pola url typu Post i 1 godzina dla pola title.
Konfigurowanie kontroli dostępu
Dołączenie dyrektywy @validateDoesLoggedInUserHaveAnyRole do pola email typu User, aby tylko administratorzy mogli odpytywać adres e-mail użytkownika.
Synchronizowanie kontroli dostępu z cache control
Łącząc dyrektywy w łańcuch, możemy zapewnić, że za każdym razem, gdy sprawdzane jest, czy użytkownik może uzyskać dostęp do pola/dyrektywy, odpowiedź nie będzie zapisywana w pamięci podręcznej. Na przykład:
- Dołącz dyrektywę
@validateIsUserLoggedIndo polame - Dołącz dyrektywę
@CacheControlz wartością0argumentumaxAgedo dyrektywy@validateIsUserLoggedIn.
Wzmacnianie bezpieczeństwa
Dołączenie dyrektywy @validateIsUserLoggedIn do dyrektywy @translate, aby uniemożliwić złośliwym podmiotom wykonywanie queries przeciwko usłudze GraphQL, które mogłyby spowodować przeciążenie serwera i wzrost rachunków (w tym przypadku @translate jest oparty na Google Translate i pobiera opłatę za korzystanie z tej usługi)
Jak to działa
W jaki sposób dodajemy dyrektywy do schematu za pomocą IFTTT? Powiedzmy na przykład, że chcemy utworzyć niestandardową dyrektywę @authorize(role: String!), aby sprawdzić, czy użytkownik wykonujący zapytanie o pole myPosts posiada oczekiwaną rolę author, lub wyświetlić błąd w przeciwnym razie.
Gdybyśmy tworzyli schemat przy użyciu SDL, wyglądałoby to tak:
directive @authorize(role: String!) on FIELD_DEFINITION
type User {
myPosts: [Post] @authorize(role: "author")
}Reguła IFTTT definiuje ten sam zamiar, który deklaruje powyższy SDL: za każdym razem, gdy żądane jest pole myPosts, wykonaj na nim dyrektywę @authorize(role: "author"). Następnie, za każdym razem, gdy pole myPosts zostanie znalezione w zapytaniu, silnik automatycznie dołączy @authorize(role: 'author') do tego pola w wykonywalnym zapytaniu.
Reguły IFTTT mogą być również wyzwalane po napotkaniu dyrektywy, nie tylko pola. Na przykład możemy skonfigurować regułę „Za każdym razem, gdy dyrektywa @translate zostanie znaleziona w zapytaniu, wykonaj dyrektywę @cache(time: 3600) na tym polu".
Dodawanie dyrektyw IFTTT do zapytania jest procesem rekurencyjnym: spowoduje to wyzwolenie nowego zdarzenia przetwarzanego przez reguły IFTTT, potencjalnie dołączając kolejne dyrektywy do zapytania i tak dalej.
Na przykład reguła „Za każdym razem, gdy dyrektywa @cache zostanie znaleziona, wykonaj dyrektywę @log" spowoduje zapisanie wpisu dotyczącego wykonania pola, a następnie wyzwoli nowe zdarzenie związane z tą nowo dodaną dyrektywą.
Konfiguracja za pomocą kodu PHP
Typ User posiada pola roles i capabilities, które można uznać za wrażliwe informacje, dlatego nie powinny być dostępne dla przypadkowego użytkownika.
Możemy zatem dołączyć dyrektywę @validateDoesLoggedInUserHaveAnyRole do tych dwóch pól, skonfigurowaną tak, aby sprawdzała, czy tylko użytkownik z określoną rolą (skonfigurowaną za pomocą zmiennej środowiskowej) może uzyskać do nich dostęp. Konfiguracja jest dostarczana za pomocą CompilerPass:
$accessControlManagerDefinition = $containerBuilderWrapper->getDefinition(AccessControlManagerInterface::class);
if ($roles = Environment::anyRoleLoggedInUserMustHaveToAccessRolesFields()) {
$accessControlManagerDefinition->addMethodCall(
'addEntriesForFields',
[
UserRolesAccessControlGroups::ROLES,
[
[RootObjectTypeResolver::class, 'roles', $roles],
[UserObjectTypeResolver::class, 'roles', $roles],
[RootObjectTypeResolver::class, 'capabilities', $roles],
[UserObjectTypeResolver::class, 'capabilities', $roles],
]
]
);
}
if ($capabilities = Environment::anyCapabilityLoggedInUserMustHaveToAccessRolesFields()) {
$accessControlManagerDefinition->addMethodCall(
'addEntriesForFields',
[
UserCapabilitiesAccessControlGroups::CAPABILITIES,
[
[RootObjectTypeResolver::class, 'roles', $capabilities],
[UserObjectTypeResolver::class, 'roles', $capabilities],
[RootObjectTypeResolver::class, 'capabilities', $capabilities],
[UserObjectTypeResolver::class, 'capabilities', $capabilities],
]
]
);
}Podczas wykonywania zapytania użytkownicy niezalogowani oraz użytkownicy bez wymaganych ról nie będą mieli dostępu do tych pól.