I was using a custom field directive called "route" which performs my conversion of a graphql request into an agnostic call so that I can support REST and graphql. This was all working perfectly in version lumen 2.2, but after upgrading to 3.3 I get the following error: "No directive found for route".
I don't see any documentation of other issues opened related to custom field directives in 3.x, so my apologies if I missed one. I appreciate any help in help figuring out what I'm missing.
Thanks!
My usage in a schema file is like this:
extend type Query {
students(filter:[QueryFilter]): [Student!]! @route(resolver: "Student@getAll" operation: "retrieve")
student(id: Int!): Student! @route(resolver: "Student@get" operation: "retrieve")
}
I have the following line in my config file (/config/lighthouse.php):
'directives' => [__DIR__.'/../app/Http/GraphQL/Directives'],
I have my route directive defined at /app/Http/GraphQL/Directives/RouteDirective.php:
`
class RouteDirective implements FieldResolver
{
use HandlesDirectives;
/**
* @var string The controller used to pass through service requests
*/
private const CONTROLLER = '\\App\\Http\\Controllers\\GraphQLController';
/**
* @var string The field resolver
*/
protected $resolver;
/**
* @var string The field resolver method
*/
protected $method;
/**
* Name of the directive.
*
* @return string
*/
public function name()
{
return 'route';
}
/**
* Resolve the field directive.
*
* @param \Nuwave\Lighthouse\Schema\Values\FieldValue The field to which this route directive refers
* @return \Nuwave\Lighthouse\Schema\Values\FieldValue Updated field value with resolver set by given parameters
*/
public function resolveField(FieldValue $value)
{
// get the directive node information
$directive = $this->fieldDirective($value->getField(), $this->name());
// get the resolver and operation
$resolver = $this->getResolver($directive);
$operation = $this->getOperation($directive);
return $value->setResolver(
// note: although root and context are not referenced here, they are NECESSARY for lighthouses internal routing
function ($root, array $args, $context = null, $info = null) use ($directive, $resolver, $operation) {
// set the resolver to context information and verify the controller route is valid
$info->serviceResolver = $resolver;
$reflect = new \ReflectionClass($this::CONTROLLER);
if (!$reflect->isInstantiable()) {
// this is a config error in the code rather than a schema issue so throw a http internal 500 error
throw new HttpException('GraphQL Controller does not exist.');
}
if (!$reflect->hasMethod($operation)) {
throw new DirectiveException(sprintf(
'Directive [%s] `operation` argument is invalid. Method does not exist in controller.',
$directive->name->value
));
}
// instantiate the controller and call the routed operation
try {
$controller = $reflect->newInstance($resolver, $args, $info);
return $controller->$operation();
} catch (BadRouteException $e) {
throw new DirectiveException(sprintf(
'Directive [%s] `resolver` argument is invalid. ' . $e->getMessage(),
$directive->name->value
));
}
}
);
}
/**
* Get resolver for route.
*
* @param \GraphQL\Language\AST\DirectiveNode The node from the route directive
* @throws \Nuwave\Lighthouse\Support\Exceptions\DirectiveException If the resolver doesn't exist in the directive
* @return string
*/
protected function getResolver(DirectiveNode $directive)
{
$resolver = $this->directiveArgValue($directive, 'resolver');
if (!$resolver) {
throw new DirectiveException(sprintf(
'Directive [%s] must have an `resolver` argument.',
$directive->name->value
));
}
return $resolver;
}
/**
* Get operation for route.
*
* @param \GraphQL\Language\AST\DirectiveNode The node from the route directive
* @throws \Nuwave\Lighthouse\Support\Exceptions\DirectiveException If the operation doesn't exist in the directive
* @return string
*/
protected function getOperation(DirectiveNode $directive)
{
$operation = $this->directiveArgValue($directive, 'operation');
if (!$operation) {
throw new DirectiveException(sprintf(
'Directive [%s] must have an `operation` argument.',
$directive->name->value
));
}
return $operation;
}
}`
After some looking and comparison between the version 2-based codebase I still had on another machine and this one, I resolved this.
A couple of notes for those trying to upgrade facing similar issues since there is currently no changelog pre-v3:
'directives' => [__DIR__.'/../app/Http/GraphQL/Directives'],
changes to
'namespaces' => [
....,
'directives' => 'App\\Http\\GraphQL\\Directives',
],
The HandlesDirectives trait was deprecated at some point (this is noted in the class comments for the old trait) and (presumably) removed with v3.0. Instead of using the trait and then using the inherited $this->fieldDirective() method to get the field, you need to extend BaseDirective and then the conversion is done automatically. $this->directiveArgValue('argumentName') can be used directly instead of the additional conversion. (See my updated directive code below for reference if helpful).
The DirectiveException was moved from Support/Exceptions to Exceptions in the lighthouse namespace.
Hope this can help someone.
`
class RouteDirective extends BaseDirective implements FieldResolver
{
/**
* @var string The controller used to pass through service requests
*/
private const CONTROLLER = '\\App\\Http\\Controllers\\GraphQLController';
/**
* @var string The field resolver
*/
protected $resolver;
/**
* @var string The field resolver method
*/
protected $method;
/**
* Name of the directive.
*
* @return string
*/
public function name()
{
return 'route';
}
/**
* Resolve the field directive.
*
* @param \Nuwave\Lighthouse\Schema\Values\FieldValue The field to which this route directive refers
* @return \Nuwave\Lighthouse\Schema\Values\FieldValue Updated field value with resolver set by given parameters
*/
public function resolveField(FieldValue $value)
{
// get the resolver and operation
$resolver = $this->getResolver();
$operation = $this->getOperation();
return $value->setResolver(
// note: although root and context are not referenced here, they are NECESSARY for lighthouses internal routing
function ($root, array $args, $context = null, $info = null) use ($resolver, $operation) {
// set the resolver to context information and verify the controller route is valid
$info->serviceResolver = $resolver;
$reflect = new \ReflectionClass($this::CONTROLLER);
if (!$reflect->isInstantiable()) {
// this is a config error in the code rather than a schema issue so throw a http internal 500 error
throw new HttpException('GraphQL Controller does not exist.');
}
if (!$reflect->hasMethod($operation)) {
throw new DirectiveException('Directive `route`: `operation` argument is invalid. Method does not exist in controller.');
}
// instantiate the controller and call the routed operation
try {
$controller = $reflect->newInstance($resolver, $args, $info);
return $controller->$operation();
} catch (BadRouteException $e) {
throw new DirectiveException('Directive `route`: `resolver` argument is invalid. ' . $e->getMessage());
}
}
);
}
/**
* Get resolver for route.
*
* @throws \Nuwave\Lighthouse\Exceptions\DirectiveException If the resolver doesn't exist in the directive
* @return string
*/
protected function getResolver()
{
$resolver = $this->directiveArgValue('resolver');
if (!$resolver) {
throw new DirectiveException('Directive `route` must have a `resolver` argument.');
}
return $resolver;
}
/**
* Get operation for route.
*
* @throws \Nuwave\Lighthouse\Exceptions\DirectiveException If the operation doesn't exist in the directive
* @return string
*/
protected function getOperation()
{
$operation = $this->directiveArgValue('operation');
if (!$operation) {
throw new DirectiveException('Directive `route` must have an `operation` argument.');
}
return $operation;
}
}
`
Most helpful comment
After some looking and comparison between the version 2-based codebase I still had on another machine and this one, I resolved this.
A couple of notes for those trying to upgrade facing similar issues since there is currently no changelog pre-v3:
'directives' => [__DIR__.'/../app/Http/GraphQL/Directives'],changes to
'namespaces' => [ ...., 'directives' => 'App\\Http\\GraphQL\\Directives', ],The HandlesDirectives trait was deprecated at some point (this is noted in the class comments for the old trait) and (presumably) removed with v3.0. Instead of using the trait and then using the inherited
$this->fieldDirective()method to get the field, you need to extend BaseDirective and then the conversion is done automatically.$this->directiveArgValue('argumentName')can be used directly instead of the additional conversion. (See my updated directive code below for reference if helpful).The DirectiveException was moved from Support/Exceptions to Exceptions in the lighthouse namespace.
Hope this can help someone.
Updated RouteDirective.php for lighthouse 3.x
`
class RouteDirective extends BaseDirective implements FieldResolver
{
}
`