Extending argument resolving
Using a parameter middleware, you can hook into the argument resolution of field/query/mutation/factory.
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): arrayIn the query above, the
$info
argument is filled with the WebonyxResolveInfo
class thanks to theResolveInfoParameterHandler parameter middleware
Inject a service from the container when you use the
@Autowire
annotationPerform 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.
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.
/**
* 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".