IFTTT tramite le direttive
Gato GraphQL offre la possibilità di implementare strategie IFTTT (If This Then That, «se questo allora quello») tramite le direttive. Queste direttive vengono aggiunte dinamicamente alla query ogni volta che un campo o una direttiva specifica è presente nella query.
In generale, le IFTTT sono regole che attivano azioni ogni volta che si verifica un evento specificato. Nel nostro caso, le coppie evento/azione sono:
- Se «il campo X viene trovato nella query» allora «collega la direttiva Y al campo X»
- Se «la direttiva Z viene trovata nella query» allora «esegui la direttiva Y prima/dopo la direttiva Z»
L'aggiunta dinamica di direttive IFTTT allo schema è un processo ricorsivo: una tale direttiva può, a sua volta, avere il proprio insieme di direttive IFTTT configurate, che vengono anch'esse aggiunte alla catena di direttive.
Dove viene utilizzato
Dietro le quinte, i client in Gato GraphQL utilizzano questo meccanismo per configurare lo schema GraphQL.
Per esempio, Access Control ci permette di selezionare le regole di controllo degli accessi da applicare alle operazioni, ai campi e alle direttive. È grazie alle IFTTT che queste regole vengono applicate a tali elementi dello schema GraphQL.

In generale, ecco alcuni casi d'uso:
Definire il max-age del cache control campo per campo
Collegare una direttiva @CacheControl a tutti i campi, personalizzando il valore del parametro maxAge: 1 anno per il campo url del Post e 1 ora per il campo title.
Configurare il controllo degli accessi
Collegare una direttiva @validateDoesLoggedInUserHaveAnyRole al campo email del tipo User, in modo che solo gli amministratori possano interrogare l'indirizzo e-mail dell'utente.
Sincronizzare il controllo degli accessi con il cache control
Concatenando le direttive, possiamo assicurarci che, ogni volta che si verifica se l'utente può accedere a un campo/direttiva, la risposta non venga messa in cache. Per esempio:
- Collegare la direttiva
@validateIsUserLoggedInal campome - Collegare la direttiva
@CacheControlcon il valore0per l'argomentomaxAgealla direttiva@validateIsUserLoggedIn.
Rafforzare la sicurezza
Collegare una direttiva @validateIsUserLoggedIn alla direttiva @translate, per evitare che attori malevoli eseguano query contro il servizio GraphQL in grado di mettere fuori uso il server e far esplodere la sua fattura (in questo caso, @translate è basato su Google Translate e prevede un costo per l'utilizzo di questo servizio)
Come funziona
Come aggiungere direttive allo schema tramite IFTTT? Supponiamo, per esempio, di voler creare una direttiva personalizzata @authorize(role: String!), per validare che l'utente che esegue il campo myPosts possieda il ruolo atteso author, oppure mostrare un errore in caso contrario.
Se avessimo creato lo schema utilizzando l'SDL, apparirebbe così:
directive @authorize(role: String!) on FIELD_DEFINITION
type User {
myPosts: [Post] @authorize(role: "author")
}La regola IFTTT definisce la stessa intenzione dichiarata dall'SDL qui sopra: ogni volta che viene richiesto il campo myPosts, esegui la direttiva @authorize(role: "author") su quel campo. Così, ogni volta che il campo myPosts viene trovato nella query, il motore collegherà automaticamente @authorize(role: 'author') a quel campo nella query eseguibile.
Le regole IFTTT possono anche essere attivate quando si incontra una direttiva, non solo un campo. Per esempio, possiamo configurare la regola «Ogni volta che la direttiva @translate viene trovata nella query, esegui la direttiva @cache(time: 3600) su quel campo».
L'aggiunta di direttive IFTTT alla query è un processo ricorsivo: attiverà un nuovo evento da elaborare tramite le regole IFTTT, collegando potenzialmente altre direttive alla query, e così via.
Per esempio, la regola «Ogni volta che la direttiva @cache viene trovata, esegui la direttiva @log» registrerebbe una voce sull'esecuzione del campo, per poi attivare un nuovo evento riguardante questa direttiva appena aggiunta.
Configurazione tramite codice PHP
Il tipo User possiede i campi roles e capabilities, che possono essere considerati informazioni sensibili e quindi non dovrebbero essere accessibili da qualsiasi utente.
Possiamo quindi collegare la direttiva @validateDoesLoggedInUserHaveAnyRole a questi due campi, configurata per validare che solo un utente con un determinato ruolo (configurato tramite una variabile d'ambiente) possa accedervi. La configurazione viene fornita tramite un 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],
]
]
);
}Durante l'esecuzione della query, gli utenti non connessi e gli utenti senza i ruoli richiesti non potranno accedere a questi campi.