# Argument Directives

Argument directives can be applied to a InputValueDefinition (opens new window).

As arguments may be contained within a list in the schema definition, you must specify what your argument should apply to in addition to its function.

You must implement exactly one of those two interfaces in order for an argument directive to work.

# Evaluation Order

The application of directives that implement the ArgDirective interface is split into three distinct phases:

  • Sanitize: Clean the input, e.g. trim whitespace. Directives can hook into this phase by implementing ArgSanitizerDirective.
  • Validate: Ensure the input conforms to the expectations, e.g. check a valid email is given
  • Transform: Change the input before processing it further, e.g. hashing passwords. Directives can hook into this phase by implementing ArgTransformerDirective
type Mutation {
  createUser(
    password: String @trim @rules(apply: ["min:10,max:20"]) @hash
  ): User
}

In the given example, Lighthouse will take the value of the password argument and:

  1. Trim any whitespace
  2. Run validation on it
  3. Hash it

# ArgSanitizerDirective

An \Nuwave\Lighthouse\Support\Contracts\ArgSanitizerDirective (opens new window) takes an incoming value and returns a new value.

Let's take a look at the built-in @trim directive.

namespace Nuwave\Lighthouse\Schema\Directives;

use Nuwave\Lighthouse\Support\Contracts\ArgDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgSanitizerDirective;

class TrimDirective extends BaseDirective implements ArgSanitizerDirective, ArgDirective
{
    public static function definition(): string
    {
        return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Run the `trim` function on an input value.
"""
directive @trim on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
GRAPHQL;
    }

    /**
     * Remove whitespace from the beginning and end of a given input.
     *
     * @param  string  $argumentValue
     */
    public function sanitize($argumentValue): string
    {
        return trim($argumentValue);
    }
}

The sanitize method takes an argument which represents the actual incoming value that is given to an argument in a query and is expected to modify the value, if needed, and return it.

For example, if we have the following schema.

type Mutation {
  createUser(name: String @trim): User
}

When you resolve the field, the argument will hold the sanitized value.

namespace App\GraphQL\Mutations;

use App\User;

class CreateUser
{
    public function __invoke($root, array $args): User
    {
        return User::create([
            // This will be the trimmed value of the `name` argument
            'name' => $args['name']
        ]);
    }
}

# ArgTransformerDirective

An \Nuwave\Lighthouse\Support\Contracts\ArgTransformerDirective (opens new window) works essentially the same as an ArgSanitizerDirective. Notable differences are:

  • The method to implement is called transform
  • Transformations are applied after validation, whereas sanitization is applied before

# ArgBuilderDirective

An \Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective (opens new window) directive allows using arguments passed by the client to dynamically modify the database query that Lighthouse creates for a field.

Currently, the following directives use the defined filters for resolving the query:

Take the following schema as an example:

type User {
  posts(category: String @eq): [Post!]! @hasMany
}

Passing the category argument will select only the user's posts where the category column is equal to the value of the category argument.

So let's take a look at the built-in @eq directive.

namespace Nuwave\Lighthouse\Schema\Directives;

use Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgDirective;

class EqDirective extends BaseDirective implements ArgBuilderDirective, ArgDirective
{
    public static function definition(): string
    {
        return /** @lang GraphQL */ <<<'GRAPHQL'
directive @eq(
  """
  Specify the database column to compare.
  Only required if database column has a different name than the attribute in your schema.
  """
  key: String
) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
GRAPHQL;
    }

    /**
     * Apply a "WHERE = $value" clause.
     *
     * @param  \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder  $builder
     * @param  mixed  $value
     * @return \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
     */
    public function handleBuilder($builder, $value): object
    {
        return $builder->where(
            $this->directiveArgValue('key', $this->nodeName()),
            $value
        );
    }
}

The handleBuilder method takes two arguments:

  • $builder The query builder for applying the additional query on to.
  • $value The value of the argument value that @eq was applied on to.

If you want to use a more complex value for manipulating a query, you can build a ArgBuilderDirective to work with lists or nested input objects. Lighthouse's @whereBetween is one example of this.

type Query {
  users(createdBetween: DateRange @whereBetween(key: "created_at")): [User!]!
    @paginate
}

input DateRange {
  from: Date!
  to: Date!
}

# ArgResolver

An \Nuwave\Lighthouse\Support\Contracts\ArgResolver (opens new window) directive allows you to compose resolvers for complex nested inputs, similar to the way that field resolvers are composed together.

For an in-depth explanation of the concept of composing arg resolvers, read the explanation of arg resolvers.

# ArgManipulator

An \Nuwave\Lighthouse\Support\Contracts\ArgManipulator (opens new window) directive can be used to manipulate the schema AST.

For example, you might want to add a directive that automagically derives the arguments for a field based on an object type. A skeleton for this directive might look something like this:

namespace App\GraphQL\Directives;

use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgManipulator;

class ModelArgsDirective extends BaseDirective implements ArgManipulator
{
    /**
     * SDL definition of the directive.
     *
     * @return string
     */
    public static function definition(): string
    {
        return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Automatically generates an input argument based on a type.
"""
directive @typeToInput(
    """
    The name of the type to use as the basis for the input type.
    """
    name: String!
) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
GRAPHQL;
    }

    /**
     * Manipulate the AST.
     *
     * @param  \Nuwave\Lighthouse\Schema\AST\DocumentAST  $documentAST
     * @param  \GraphQL\Language\AST\InputValueDefinitionNode  $argDefinition
     * @param  \GraphQL\Language\AST\FieldDefinitionNode  $parentField
     * @param  \GraphQL\Language\AST\ObjectTypeDefinitionNode  $parentType
     * @return void
     */
    public function manipulateArgDefinition(
        DocumentAST &$documentAST,
        InputValueDefinitionNode &$argDefinition,
        FieldDefinitionNode &$parentField,
        ObjectTypeDefinitionNode &$parentType
    ): void {
        $typeName = $this->directiveArgValue('name');
        $type = $documentAST->types[$typeName];

        $input = $this->generateInputFromType($type);
        $argDefinition->name->value = $input->value->name;

        $documentAST->setTypeDefinition($input);
    }

    protected function generateInputFromType(ObjectTypeDefinitionNode $type): InputObjectTypeDefinitionNode
    {
        // TODO generate this type based on rules and conventions that work for you
    }
}