Skip to content

[Documentation] How to organize resolvers with resolver map #433

@benjamindulau

Description

@benjamindulau
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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions