IFTTT por meio de directives
O Gato GraphQL oferece a capacidade de implementar estratégias IFTTT (If This Then That, "se isso então aquilo") por meio de directives. Essas directives são adicionadas dinamicamente à query sempre que um campo ou directive específica está presente na query.
Em geral, as IFTTT são regras que disparam ações sempre que um evento especificado ocorre. No nosso caso, os pares evento/ação são:
- Se "o campo X é encontrado na query" então "anexa a directive Y ao campo X"
- Se "a directive Z é encontrada na query" então "executa a directive Y antes/depois da directive Z"
Adicionar dinamicamente directives IFTTT ao schema é um processo recursivo: tal directive pode, por sua vez, ter seu próprio conjunto de directives IFTTT configuradas, que também são adicionadas à cadeia de directives.
Onde é utilizado
Nos bastidores, os clientes do Gato GraphQL usam esse mecanismo para configurar o schema GraphQL.
Por exemplo, o Access Control nos permite selecionar quais regras de controle de acesso aplicar a operações, campos e directives. É por meio das IFTTT que essas regras são aplicadas nesses elementos do schema GraphQL.

Em geral, estes são alguns casos de uso:
Definir o max-age do cache control campo a campo
Anexar uma directive @CacheControl a todos os campos, personalizando o valor do parâmetro maxAge: 1 ano para o campo url do Post e 1 hora para o campo title.
Configurar o controle de acesso
Anexar uma directive @validateDoesLoggedInUserHaveAnyRole ao campo email do tipo User, para que apenas os administradores possam consultar o e-mail do usuário.
Sincronizar o controle de acesso com o cache control
Encadeando directives, podemos garantir que, sempre que for validado se o usuário pode acessar um campo/directive, a resposta não seja armazenada em cache. Por exemplo:
- Anexar a directive
@validateIsUserLoggedInao campome - Anexar a directive
@CacheControlcom o valor0para o argumentomaxAgeà directive@validateIsUserLoggedIn.
Reforçar a segurança
Anexar uma directive @validateIsUserLoggedIn à directive @translate, para evitar que atores maliciosos executem queries contra o serviço GraphQL que possam derrubar o servidor e elevar sua fatura (neste caso, @translate é baseado no Google Translate e cobra uma taxa pelo uso do serviço)
Como funciona
Como adicionamos directives ao schema via IFTTT? Digamos, por exemplo, que queremos criar uma directive personalizada @authorize(role: String!), para validar que o usuário que executa o campo myPosts possui o papel esperado author, ou exibir um erro caso contrário.
Se tivéssemos criado o schema usando o SDL, ele ficaria assim:
directive @authorize(role: String!) on FIELD_DEFINITION
type User {
myPosts: [Post] @authorize(role: "author")
}A regra IFTTT define a mesma intenção que o SDL acima está declarando: sempre que o campo myPosts for requisitado, execute a directive @authorize(role: "author") nele. Então, sempre que o campo myPosts for encontrado na query, o motor anexará automaticamente @authorize(role: 'author') a esse campo na query executável.
As regras IFTTT também podem ser disparadas ao encontrar uma directive, não apenas um campo. Por exemplo, podemos configurar a regra "Sempre que a directive @translate for encontrada na query, execute a directive @cache(time: 3600) nesse campo".
Adicionar directives IFTTT à query é um processo recursivo: isso disparará um novo evento a ser processado pelas regras IFTTT, potencialmente anexando outras directives à query, e assim por diante.
Por exemplo, a regra "Sempre que a directive @cache for encontrada, execute a directive @log" registraria uma entrada sobre a execução do campo e, em seguida, dispararia um novo evento relacionado a essa directive recém-adicionada.
Configuração via código PHP
O tipo User possui os campos roles e capabilities, que podem ser considerados informações sensíveis e, portanto, não deveriam ser acessíveis por qualquer usuário.
Podemos então anexar a directive @validateDoesLoggedInUserHaveAnyRole a esses dois campos, configurada para validar que apenas um usuário com um determinado papel (configurado via variável de ambiente) possa acessá-los. A configuração é fornecida por meio de um 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],
]
]
);
}Ao executar a query, usuários não autenticados e usuários sem os papéis necessários não terão permissão para acessar esses campos.