Graphqlbundle: [Documentation] How to organize resolvers with resolver map

Created on 20 Dec 2018  路  6Comments  路  Source: overblog/GraphQLBundle

| Q | A
| ---------------- | -----
| Bug report? | no
| Feature request? | no
| BC Break report? | no
| RFC? | no
| Version/Branch | 0.11.10

I really struggle with the Resolver map and how I should organize things.

I'd like to abstract some of the work to reduce verbosity.

My goal is to obtain something as straightforward as the following:

type Query {
  user(id: ID!): User
}

type User {
  id: ID!
  email: String!
  firstName: String!
  lastName: String!
  avatar: Avatar!
}

type Avatar {
  url: String!
  placeholder: Boolean!
}
<?php
namespace ST\GraphQL\Resolver;


use Overblog\GraphQLBundle\Resolver\ResolverMap;
use GraphQL\Type\Definition\ResolveInfo;
use Overblog\GraphQLBundle\Definition\Argument;

class MainResolverMap extends ResolverMap
{
    private $queryResolver;
    private $userResolver;

    public function __construct(QueryResolver $queryResolver, UserResolver $userResolver)
    {
        $this->queryResolver = $queryResolver;
        $this->userResolver = $userResolver;
    }

    protected function map()
    {
        return [
            'Query' => $this->queryResolver,
            'User'  => $this->userResolver,
        ];
    }
}
<?php
namespace ST\GraphQL\Resolver;

use Overblog\GraphQLBundle\Definition\Argument;
use GraphQL\Type\Definition\ResolveInfo;
use ST\GraphQL\DataFetching\DataLoader;

class UserResolver
{
    private $dataLoader;

    public function __construct(DataLoader $dataLoader)
    {
        $this->dataLoader = $dataLoader;
    }

    public function resolveAvatar($value, Argument $args, \ArrayObject $context, ResolveInfo $info): array
    {
        // this would use some injected dependency
        return [
            'url' => 'http://....' . $value['id'] . '.jpg',
            'placeholder' => false,
        ];
    }
}

But because of the way the resolver map works, I can't figure out how to implement it this way.

Note that the following works:

class MainResolverMap extends ResolverMap
{
    private $userResolver;

    public function __construct(UserResolver $userResolver)
    {
        $this->userResolver = $userResolver;
    }

    protected function map()
    {
        return [
            'Query' => ...,
            'User' => [
                'avatar' => function ($value, Argument $args, \ArrayObject $context, ResolveInfo $info) {
                   return $this->userResolver->resolveAvatar($value, $args, $context, $info);
                }
            ]
        ];
    }
}

But it's clearly very verbose and would become a pain to maintain on a large project!
I could move the User field map inside the UserResolver class, but still, it's too much verbosity.

Also tried some naive approaches like the following:

abstract class AbstractTypeResolver
{
    public function getResolveMap(): array
    {
        return [
            ResolverMap::RESOLVE_FIELD => function ($value, Argument $args, \ArrayObject $context, ResolveInfo $info) {
                $resolveMethod = 'resolve' . \ucfirst($info->fieldName);

                if (\method_exists($this, $resolveMethod)) {
                    return call_user_func([$this, $resolveMethod], $value, $args, $context, $info);
                }

                // Not good
                return $value[$info->fieldName];
            }
        ];
    }
}
class MainResolverMap extends ResolverMap
{
    private $queryResolver;
    private $userResolver;

    public function __construct(QueryResolver $queryResolver, UserResolver $userResolver)
    {
        $this->queryResolver = $queryResolver;
        $this->userResolver = $userResolver;
    }

    protected function map()
    {
        return [
            'Query' => $this->queryResolver->getResolveMap(),
            'User' => $this->userResolver->getResolveMap(),
        ];
    }
}

And then having UserResolver extending AbstractTypeResolver but it doesn't seem right.

In the end I just want to avoid having to repeat that a million time:

'avatar' => function ($value, Argument $args, \ArrayObject $context, ResolveInfo $info) {
    return $this->userResolver->resolveAvatar($value, $args, $context, $info);
}

And factor this logic somewhere hidden.

Any thoughts or recommendations?

question

All 6 comments

Can you try using callable resolver with __invoke method?

@mcg-web You mean using tagged resolver services instead of the ResolverMap? Like the following?

class Greetings implements ResolverInterface
{
    public function __invoke(ResolveInfo $info, $name)
    {
        if($info->fieldName === 'hello'){
            return sprintf('hello %s!!!', $name);
        }
        else if($info->fieldName === 'goodbye'){
            return sprintf('goodbye %s!!!', $name);
        }
        else{
            throw new \DomainException('Unknown greetings');
        }
    }
}

@mcg-web Same issue for me, could someone show better example how resolver und map?

@riroxumigu this could maybe help you. I'll try to work on a resolver map using tagged "classic resolver" in the coming days.

@mcg-web thank You! Thats helped but now I can't execute mutation method to update entity. I did eveyrthing like this https://github.com/overblog/GraphQLBundle/blob/master/docs/definitions/graphql-schema-language.md#define-mutations and class PostMutation which implements MutationInterface.

@mcg-web any news?

Was this page helpful?
0 / 5 - 0 ratings