Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
benjamindulau opened this issue Dec 20, 2018 · 6 comments
Open

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

benjamindulau opened this issue Dec 20, 2018 · 6 comments
Labels

Comments

@benjamindulau
Copy link

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?

@mcg-web
Copy link
Member

mcg-web commented Dec 21, 2018

Can you try using callable resolver with __invoke method?

@benjamindulau
Copy link
Author

@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');
        }
    }
}

@riroxumigu
Copy link

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

@mcg-web
Copy link
Member

mcg-web commented Feb 1, 2019

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

@riroxumigu
Copy link

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

@armetiz
Copy link

armetiz commented May 6, 2019

@mcg-web any news?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants