Skip to main content
Version: 4.3

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".