IFTTT a través de directivas
Gato GraphQL ofrece la posibilidad de implementar estrategias IFTTT (If This Then That, "si esto entonces aquello") mediante directivas. Estas directivas se añaden dinámicamente a la consulta cada vez que algún campo o directiva específico está presente en ella.
En general, IFTTT son reglas que desencadenan acciones cada vez que ocurre un evento determinado. En nuestro caso, los pares evento/acción son:
- Si "se encuentra el campo X en la consulta" entonces "adjuntar la directiva Y al campo X"
- Si "se encuentra la directiva Z en la consulta" entonces "ejecutar la directiva Y antes/después de la directiva Z"
Añadir dinámicamente directivas IFTTT al esquema es un proceso recursivo: una directiva así puede, a su vez, tener configurado su propio conjunto de directivas IFTTT, que también se añaden a la cadena de directivas.
Dónde se utiliza
Internamente, los clientes en Gato GraphQL utilizan este mecanismo para configurar el esquema GraphQL.
Por ejemplo, Access Control nos permite seleccionar qué reglas de control de acceso aplicar a operaciones, campos y directivas. Es mediante IFTTT como estas reglas se aplican a esos elementos del esquema GraphQL.

En general, estos son algunos casos de uso:
Definir el max-age de cache control campo por campo
Adjuntar una directiva @CacheControl a todos los campos, personalizando el valor del parámetro maxAge: 1 año para el campo url del Post, y 1 hora para el campo title.
Configurar el control de acceso
Adjuntar una directiva @validateDoesLoggedInUserHaveAnyRole al campo email del tipo User, de modo que solo los administradores puedan consultar el correo electrónico del usuario.
Sincronizar el control de acceso con el cache control
Encadenando directivas, podemos asegurarnos de que, siempre que se valide si el usuario puede acceder a un campo/directiva, la respuesta no se almacene en caché. Por ejemplo:
- Adjuntar la directiva
@validateIsUserLoggedInal campome - Adjuntar la directiva
@CacheControlcon el valor0para el argumentomaxAgea la directiva@validateIsUserLoggedIn.
Reforzar la seguridad
Adjuntar una directiva @validateIsUserLoggedIn a la directiva @translate, para evitar que actores maliciosos ejecuten consultas contra el servicio GraphQL que puedan tumbar el servidor y disparar su factura (en este caso, @translate se basa en Google Translate y paga una tarifa por usar este servicio)
Cómo funciona
¿Cómo añadimos directivas al esquema mediante IFTTT? Supongamos, por ejemplo, que queremos crear una directiva personalizada @authorize(role: String!), para validar que el usuario que ejecuta el campo myPosts tiene el rol esperado author, o mostrar un error en caso contrario.
Si hubiéramos creado el esquema mediante SDL, tendría este aspecto:
directive @authorize(role: String!) on FIELD_DEFINITION
type User {
myPosts: [Post] @authorize(role: "author")
}La regla IFTTT define la misma intención que declara el SDL anterior: siempre que se solicite el campo myPosts, ejecutar la directiva @authorize(role: "author") sobre él. Entonces, cada vez que se encuentre el campo myPosts en la consulta, el motor adjuntará automáticamente @authorize(role: 'author') a ese campo en la consulta ejecutable.
Las reglas IFTTT también pueden dispararse al encontrar una directiva, no solo un campo. Por ejemplo, podemos configurar la regla "Siempre que se encuentre la directiva @translate en la consulta, ejecutar la directiva @cache(time: 3600) sobre ese campo".
Añadir directivas IFTTT a la consulta es un proceso recursivo: desencadenará un nuevo evento que será procesado por las reglas IFTTT, pudiendo adjuntar otras directivas a la consulta, y así sucesivamente.
Por ejemplo, la regla "Siempre que se encuentre la directiva @cache, ejecutar la directiva @log" registraría una entrada sobre la ejecución del campo y desencadenaría a su vez un nuevo evento relacionado con esta directiva recién añadida.
Configurándolo mediante código PHP
El tipo User tiene los campos roles y capabilities, que pueden considerarse información sensible, por lo que no deberían ser accesibles para cualquier usuario.
Así que podemos adjuntar la directiva @validateDoesLoggedInUserHaveAnyRole a estos dos campos, configurada para validar que solo un usuario con un rol determinado (configurado mediante una variable de entorno) pueda acceder a ellos. La configuración se proporciona a través de 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],
]
]
);
}Al ejecutar la consulta, los usuarios que no hayan iniciado sesión y los usuarios sin los roles requeridos no podrán acceder a esos campos.