Skip to main content
Version: 7.0.0

Extending argument resolving

Available in GraphQLite 4.0+

Using a parameter middleware, you can hook into the argument resolution of field/query/mutation/factory.

Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)

As an example, GraphQLite uses parameter middlewares internally to:

  • Inject the Webonyx GraphQL resolution object when you type-hint on the ResolveInfo object. For instance:

    /**
    * @return Product[]
    */
    #[Query]
    public function products(ResolveInfo $info): array

    In the query above, the $info argument is filled with the Webonyx ResolveInfo class thanks to the ResolveInfoParameterHandler parameter middleware

  • Inject a service from the container when you use the @Autowire annotation

  • Perform validation with the @Validate annotation (in Laravel package)

Parameter middlewares

Each middleware is passed number of objects describing the parameter:

  • a PHP ReflectionParameter object representing the parameter being manipulated
  • a phpDocumentor\Reflection\DocBlock instance (useful to analyze the @param comment if any)
  • a phpDocumentor\Reflection\Type instance (useful to analyze the type if the argument)
  • a TheCodingMachine\GraphQLite\Annotations\ParameterAnnotations instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)
  • a $next handler to pass the argument resolving to the next middleware.

Parameter resolution is done in 2 passes.

On the first pass, middlewares are traversed. They must return a TheCodingMachine\GraphQLite\Parameters\ParameterInterface (an object that does the actual resolving).

interface ParameterMiddlewareInterface
{
public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;
}

Then, resolution actually happen by executing the resolver (this is the second pass).

Annotations parsing

If you plan to use annotations while resolving arguments, your annotation should extend the ParameterAnnotationInterface

For instance, if we want GraphQLite to inject a service in an argument, we can use @Autowire(for="myService").

For PHP 8 attributes, we only need to put declare the annotation can target parameters: #[Attribute(Attribute::TARGET_PARAMETER)].

The annotation looks like this:

use Attribute;

/**
* Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.
*
* @Annotation
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
class Autowire implements ParameterAnnotationInterface
{
/**
* @var string
*/
public $for;

/**
* The getTarget method must return the name of the argument
*/
public function getTarget(): string
{
return $this->for;
}
}

Writing the parameter middleware

The middleware purpose is to analyze a parameter and decide whether or not it can handle it.

Parameter middleware class
class ContainerParameterHandler implements ParameterMiddlewareInterface
{
/** @var ContainerInterface */
private $container;

public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface
{
// The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface
$autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);

if ($autowire === null) {
// If there are no annotation, this middleware cannot handle the parameter. Let's ask
// the next middleware in the chain (using the $next object)
return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);
}

// We found a @Autowire annotation, let's return a parameter resolver.
return new ContainerParameter($this->container, $parameter->getType());
}
}

The last step is to write the actual parameter resolver.

Parameter resolver class
/**
* A parameter filled from the container.
*/
class ContainerParameter implements ParameterInterface
{
/** @var ContainerInterface */
private $container;
/** @var string */
private $identifier;

public function __construct(ContainerInterface $container, string $identifier)
{
$this->container = $container;
$this->identifier = $identifier;
}

/**
* The "resolver" returns the actual value that will be fed to the function.
*/
public function resolve(?object $source, array $args, $context, ResolveInfo $info)
{
return $this->container->get($this->identifier);
}
}

Registering a parameter middleware

The last step is to register the parameter middleware we just wrote:

You can register your own parameter middlewares using the SchemaFactory::addParameterMiddleware() method.

$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));

If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".