graphql-php icon indicating copy to clipboard operation
graphql-php copied to clipboard

Examples of custom directives

Open vladar opened this issue 6 years ago • 5 comments

We need some examples in the documentation on how to implement custom directives both for query resolution and schema definitions.

vladar avatar Jul 12 '18 19:07 vladar

Here is an example of a @cache(ttl: Int!) directive.

# Directive definition in GraphQL language
# I am using GraphQL language in this example
# If you are using PHP definition, please refer to \GraphQL\Type\Definition\Directive::getInternalDirectives
directive @cache(
    ttl: Int!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
// Define some helper functions to get the directive in the current node
class Util
{
    /**
     * @param ResolveInfo $info
     * @param string $name
     * @return DirectiveNode|null
     */
    public static function getDirectiveByName(ResolveInfo $info, string $name)
    {
        $fieldNode = $info->fieldNodes[0];
        /** @var NodeList $directives */
        $directives = $fieldNode->directives;
        if ($directives) {
            /** @var DirectiveNode[] $directives */
            foreach ($directives as $directive) {
                if ($directive->name->value === $name) {
                    return $directive;
                }
            }
        }
        return null;
    }

    /**
     * @param DirectiveNode $directive
     * @return ValueNode[]
     */
    public static function getDirectiveArguments(DirectiveNode $directive)
    {
        $args = [];
        foreach ($directive->arguments as $arg) {
            $args[$arg->name->value] = $arg->value;
        }
        return $args;
    }
}
# Usage
class MyType
{
    public static function myNode($parent, array $args, ContextImmutable $context, ResolveInfo $info)
    {
        $fieldName = $info->fieldName;

        $cacheDirective = Util::getDirectiveByName($info, 'cache');
        if ($cacheDirective) {
            $cacheDirectiveArgs = Util::getDirectiveArguments($cacheDirective);
            /** @var \GraphQL\Language\AST\IntValueNode $ttlArg */
            // Please node IntValueNode is not the only type, there are StringValueNode, etc
            $ttlArg = $cacheDirectiveArgs['ttl'];
            $ttl = (int)$ttlArg->value;
        }
    }
}

I got help from the answer provided by @vladar in https://github.com/webonyx/graphql-php/issues/299#issuecomment-417062970

# GraphQL usage
query {
  myType {
    myNode @cache(ttl: 60) # cache for 60 seconds
  }
}

zorji avatar Oct 18 '18 23:10 zorji

Here is an example of a @cache(ttl: Int!) directive.

# Directive definition in GraphQL language
# I am using GraphQL language in this example
# If you are using PHP definition, please refer to \GraphQL\Type\Definition\Directive::getInternalDirectives
directive @cache(
    ttl: Int!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
// Define some helper functions to get the directive in the current node
class Util
{
    /**
     * @param ResolveInfo $info
     * @param string $name
     * @return DirectiveNode|null
     */
    public static function getDirectiveByName(ResolveInfo $info, string $name)
    {
        $fieldNode = $info->fieldNodes[0];
        /** @var NodeList $directives */
        $directives = $fieldNode->directives;
        if ($directives) {
            /** @var DirectiveNode[] $directives */
            foreach ($directives as $directive) {
                if ($directive->name->value === $name) {
                    return $directive;
                }
            }
        }
        return null;
    }

    /**
     * @param DirectiveNode $directive
     * @return ValueNode[]
     */
    public static function getDirectiveArguments(DirectiveNode $directive)
    {
        $args = [];
        foreach ($directive->arguments as $arg) {
            $args[$arg->name->value] = $arg->value;
        }
        return $args;
    }
}
# Usage
class MyType
{
    public static function myNode($parent, array $args, ContextImmutable $context, ResolveInfo $info)
    {
        $fieldName = $info->fieldName;

        $cacheDirective = Util::getDirectiveByName($info, 'cache');
        if ($cacheDirective) {
            $cacheDirectiveArgs = Util::getDirectiveArguments($cacheDirective);
            /** @var \GraphQL\Language\AST\IntValueNode $ttlArg */
            // Please node IntValueNode is not the only type, there are StringValueNode, etc
            $ttlArg = $cacheDirectiveArgs['ttl'];
            $ttl = (int)$ttlArg->value;
        }
    }
}

I got help from the answer provided by @vladar in #299 (comment)

# GraphQL usage
query {
  myType {
    myNode @cache(ttl: 60) # cache for 60 seconds
  }
}

your solution is not perfect. If I define @date direct to transform timestamp to date, And I must to process the same directive in every field resolver?Why not process in custom type?

Chrisdowson avatar Oct 19 '19 08:10 Chrisdowson

Here is an example of a @cache(ttl: Int!) directive.

# Directive definition in GraphQL language
# I am using GraphQL language in this example
# If you are using PHP definition, please refer to \GraphQL\Type\Definition\Directive::getInternalDirectives
directive @cache(
    ttl: Int!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
// Define some helper functions to get the directive in the current node
class Util
{
    /**
     * @param ResolveInfo $info
     * @param string $name
     * @return DirectiveNode|null
     */
    public static function getDirectiveByName(ResolveInfo $info, string $name)
    {
        $fieldNode = $info->fieldNodes[0];
        /** @var NodeList $directives */
        $directives = $fieldNode->directives;
        if ($directives) {
            /** @var DirectiveNode[] $directives */
            foreach ($directives as $directive) {
                if ($directive->name->value === $name) {
                    return $directive;
                }
            }
        }
        return null;
    }

    /**
     * @param DirectiveNode $directive
     * @return ValueNode[]
     */
    public static function getDirectiveArguments(DirectiveNode $directive)
    {
        $args = [];
        foreach ($directive->arguments as $arg) {
            $args[$arg->name->value] = $arg->value;
        }
        return $args;
    }
}
# Usage
class MyType
{
    public static function myNode($parent, array $args, ContextImmutable $context, ResolveInfo $info)
    {
        $fieldName = $info->fieldName;

        $cacheDirective = Util::getDirectiveByName($info, 'cache');
        if ($cacheDirective) {
            $cacheDirectiveArgs = Util::getDirectiveArguments($cacheDirective);
            /** @var \GraphQL\Language\AST\IntValueNode $ttlArg */
            // Please node IntValueNode is not the only type, there are StringValueNode, etc
            $ttlArg = $cacheDirectiveArgs['ttl'];
            $ttl = (int)$ttlArg->value;
        }
    }
}

I got help from the answer provided by @vladar in #299 (comment)

# GraphQL usage
query {
  myType {
    myNode @cache(ttl: 60) # cache for 60 seconds
  }
}

your solution is not perfect. If I define @date direct to transform timestamp to date, And I must to process the same directive in every field resolver?Why not process in custom type?

Why would you do that with a directive? Your use case sounds like a scalar type use case. But anyway my example is very likely not perfect. It just did what I need and is just use as an example here. Feel free to create a better example. And it's probably out-dated already.

zorji avatar Oct 19 '19 08:10 zorji

I would love to see more examples of custom directives, the documentation still has me scratching my head. Looking at the $trackDirective, I don't know where it is to be placed in my schema. I don't understand how to use a @skip directive for example when my schema is entirely defined in PHP; I have only seen examples of directive when it is expressive in the type language (GSL?). What is the FieldArgument role? Is it something found in the target field. Directives seem powerful, but I'd love to see the doc expanded, or a directive used in the Blog example, from which I learned a lot. Thanks

sebastienbarre avatar Aug 14 '20 02:08 sebastienbarre

Working on custom directive integration in a project, I found the GraphQL\Executor\Values able to perform directive detection and argument type casting:

$cacheDirective= Values::getDirectiveValues(
    $resolveInfo->schema->getDirective('cache'),
    $resolveInfo->fieldNodes[0],
    $resolveInfo->variableValues
);

if ($cacheDirective) {
    $ttl = $cacheDirective['ttl'];
}

It will convert the TTL argument value to match the schema definition type and return null if it cannot find the directive.

matthieu88160 avatar Oct 29 '21 09:10 matthieu88160