Graphqlbundle: Symfony Flex | Resolver usage

Created on 21 Oct 2017  路  17Comments  路  Source: overblog/GraphQLBundle

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

Hello everyone,

As described in the symfony/recipes-contrib repository, I've tried to install the bundle using the recipes allowed, I've finally succeeded to create types and schema but I met a problem with the resolvers, here's the problem.

I've defined an ImageResolver who's responsible for resolving all the images (or a single one) stored in the BDD (PostgreSQL via Doctrine), here's the class :

<?php

/*
 * This file is part of the MarketReminder project.
 *
 * (c) Guillaume Loulier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Resolvers;

use App\Models\Image;
use Doctrine\ORM\EntityManagerInterface;
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;

/**
 * Class ImageResolver
 *
 * @author Guillaume Loulier <[email protected]>
 */
class ImageResolver implements ResolverInterface
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * ImageResolver constructor.
     *
     * @param EntityManagerInterface $entityManagerInterface
     */
    public function __construct(EntityManagerInterface $entityManagerInterface)
    {
        $this->entityManager = $entityManagerInterface;
    }

    /**
     * @param Argument $argument
     *
     * @return Image|Image[]|array|null|object
     */
    public function getImage(Argument $argument)
    {
        if ($id = $argument->offsetExists('id')) {
            return $this->entityManager->getRepository(Image::class)
                                       ->findOneBy([
                                           'id' => $id
                                       ]);
        }

        return $this->entityManager->getRepository(Image::class)->findAll();
    }
}

As define in the documentation, here's my services.yml :

parameters:
    locale: 'en'

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Repository}'

    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    graphql.image_resolver:
        class: App\Resolvers\ImageResolver
        arguments:
            - '@doctrine.orm.entity_manager'
        tags:
            - { name: overblog_graphql.resolver, alias: 'image_id', method: 'getImage' }

The tag seems good and the method call is exactly like the method declaration, in accord with the documentation, here's the query.yaml file :

Query:
    type: object
    config:
        description:
        fields:
            user:
                type: "User"
                description: "An user registered into the application"
                args:
                    id:
                        type: "Int"
                        description: "The id of the User"
                resolve: "@resolver=('user_id', [args])"
            image:
                type: "Image"
                description: "An image linked to an user"
                args:
                    id:
                        type: "Int"
                        description: "The id of the Image"
                resolve: "@marketReminder.graphql.image_resolver=('image_id', [args])"
            stock:
                type: "Stock"
                description: "A stock saved and linked to an user"
                args:
                    id:
                        type: "Int"
                        description: "The id of the Stock"
                resolve: "@resolver=('stock_id', [args])"
            products:
                type: "Products"
                description: "A product saved and linked to a stock"
                args:
                    id:
                        type: "Int"
                        description: "The id of the product"
                resolve: "@resolver=('products_id', [args])"

As you can see, the resolve is called in the Image definition and he must return the image(s) as defined in the resolver, problem is, if I use the /graphiql endpoint and ask for something like this :

{
  image(id: 1) {
    alt
  }
}

Here's the return :

{
  "data": {
    "image": {
      "alt": null
    }
  }
}

The fixtures is loaded and the PHPStorm interface return data into the BDD so I guess the problem comes from the resolver or the bundle linking process between the requested resource and the endpoint return.

I know that I can ask in the symfony-devs Slack but it seems that no one has use the bundle since he's allowed by Flex so I come here to have the "creator" vision for my problem, I know it's not really an issue and more a "configuration" or "classe definition" problem but it really block me and I don't know how to solve this problem.

Plus, I've read the documentation for days and I find that the resolver part it's not pretty clear, the ResolverInterface is empty (who's strange for an interface ?) and there's not example in order to build a simple resolver, maybe this part can be completed ?

Thanks again for the help and have a great day :).

question

Most helpful comment

Finally I found mistake in my definition.

The final solution is

'@=resolver("App\\GraphQL\\Resolver\\UserResolver", [args])'

(define Resolver without "s" :man_facepalming: )

and resolver class I implement from definition Resolver interface.
This time all works as expected. Thanks

All 17 comments

Hi,

It look like your resolver returns a collection while your Schema expects a single Image.

            image:
                type: "Image"
                description: "An image linked to an user"
                args:
                    id:
                        type: "Int"
                        description: "The id of the Image"
                resolve: "@marketReminder.graphql.image_resolver=('image_id', [args])"

Have you define a custom expression language objet marketReminder?

Also sorry for the wait...

Hi @mcg-web, no problem for the wait.

Yes, after looking at my code, I found that my resolvers are badly declared, here's the new services.yml :

parameters:
    locale: 'en'

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Repository}'

    App\Action\:
        resource: '../src/Action'
        tags: ['controller.service_arguments']

    graphql.image_resolver:
        class: App\Resolvers\ImageResolver
        arguments:
            - '@doctrine.orm.entity_manager'
        tags:
            - { name: overblog_graphql.resolver, alias: 'image_resolver', method: getImage }

    graphql.user_resolver:
        class: App\Resolvers\UserResolver
        arguments:
            - '@doctrine.orm.entity_manager'
        tags:
            - { name: overblog_graphql.resolver, alias: 'user_resolver', method: getUser }
            - { name: overblog_graphql.resolver, alias: 'users_resolver', method: getUsers }

    graphql.stock_resolver:
        class: App\Resolvers\StockResolver
        arguments:
            - '@doctrine.orm.entity_manager'
        tags:
            - { name: overblog_graphql.resolver }

    graphql.products_resolver:
        class: App\Resolvers\ProductsResolver
        arguments:
            - '@doctrine.orm.entity_manager'
        tags:
            - { name: overblog_graphql.resolver }

    graphql.user_mutator:
        class: App\Mutators\UserMutator
        tags:
            - { name: overblog_graphql.mutation, method: register }
            - { name: overblog_graphql.mutation, method: login }
            - { name: overblog_graphql.mutation, method: forgotPassword }
            - { name: overblog_graphql.mutation, method: dropUser }

    graphql.image_mutator:
        class: App\Mutators\ImageMutator
        tags:
            - { name: overblog_graphql.mutation, alias: 'image_creation', method: createImage }

    graphql.stock_mutator:
        class: App\Mutators\StockMutator
        tags:
            - { name: overblog_graphql.mutation, alias: 'stock_creation', method: createStock }

The alias key is mandatory (this could be added to the documentation because, without this key, the "@=service" call is mandatory in order to make the resolver work as expected) as I saw from here.

After this, here's the query.yaml :

```yml
Query:
type: object
config:
description:
fields:
user:
type: "User"
description: "An user registered into the application"
args:
id:
type: "Int"
description: "The id of the User, if omitted, return all the users."
resolve: "@=resolver('user_resolver', [args])"
users:
type: "[User]"
description: "All the users saved."
resolve: "@=resolver('users_resolver')"
image:
type: "[Image]"
description: "An image linked to an user"
args:
id:
type: "Int"
description: "The id of the Image, if omitted, all the images are returned."
resolve: "@=resolver('image_resolver', [args])"
stock:
type: "[Stock]"
description: "A stock saved and linked to an user"
args:
id:
type: "Int"
description: "The id of the Stock"
resolve: "@=service('graphql.stock_resolver').getStock(args)"
products:
type: "[Products]"
description: "A product saved and linked to a stock, if omitted, return all the stocks"
args:
id:
type: "Int"
description: "The id of the product, if omitted, return all the products."
resolve: "@=service('graphql.products_resolver').getProduct(args)"
````
Using this approach (the service call for stock and products are just for placeholding), I can reach the resolver and get all the images or a single one (thanks to Youtube -
API GraphQL
, you can return a single occurence using the [] approach).

For the documentation, it could be usefull to update the docs according to a practical example, don't know exactly what but an example using a Foo object or even an Article can help to configure the bundle.

PS : Yes, for the marketReminder object, it was an error.

No alias should not be a mandatory, when not using alias key the alias is the fully qualified method name (example: AppBunble\GraphQL\CustomResolver::myMethod) if this is not the case this is a bug. Yes I agree that the doc can be perfected, all contribution is welcome.

After reconfiguring my resolvers, I can assure that the alias is "mandatory", if not defined, the resolver isn't found.

For the documentation, yes, definitively gonna look for adding more "examples" and explanation about the configuration and definition part :)

@mcg-web

No alias should not be a mandatory, when not using alias key the alias is the fully qualified method name (example: AppBunble\GraphQL\CustomResolver::myMethod) if this is not the case this is a bug.

in my case I have next config of services:

services:
    App\GraphQL\Resolver\:
        resource: '../src/GraphQL/Resolver'
        tags: ['overblog_graphql.resolver']

and I'm trying to use resolver

Query:
    type: object
    config:
        description: "Users"
        fields:
            user:
                type: "User"
                args:
                    id:
                        type: "Int"
                resolve: "@=resolver('\\App\\GraphQL\\Resolver\\UserResolver', [args['id']])"

UserResolver.php

class UserResolver
{
    private static $users = [
        1 => ['id' => 1, 'username' => 'Sasha'],
        2 => ['id' => 2, 'username' => 'Dima'],
    ];

    public function __invoke($data)
    {
        if (isset(self::$users[$data])) {
            return self::$users[$data];
        }

        return null;
    }

}

but have exception

Unknown resolver with alias \"AppGraphQLResolverUserResolver\" (verified service tag)

@Besedin86

What if you use :

@=resolver("App\GraphQL\Resolvers\UserResolver", [args])

Seems like the namespace call isn't good.

Yaml format will be not valid. I tried different ways

This solution should works with simple quote '@=resolver("App\GraphQL\Resolvers\UserResolver", [args])'

Also no need to declare resolver if you using 0.9 version it is auto-configure...

This solution should works with simple quote '@=resolver("App\GraphQL\Resolvers\UserResolver", [args])'

No luck. Have the same exception. It cleanups FQCN from backslashes

can you try this please '@=resolver(\'App\\GraphQL\\Resolvers\\UserResolver\', [args])'

only example I found in the code add slashes like this. but the best solution is to implements interface Resolver aliases (Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface and Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface) and use it in expression language see https://github.com/overblog/GraphQLBundle/blob/master/Resources/doc/definitions/resolver.md so no need to load your resolvers like this.

services:
    App\GraphQL\Resolver\:
        resource: '../src/GraphQL/Resolver'
        tags: ['overblog_graphql.resolver']

can you try this please '@=resolver(\'App\GraphQL\Resolvers\UserResolver\', [args])'

This is not valid YAML format;
I worked around this and found in \Overblog\GraphQLBundle\__DEFINITIONS__\QueryType::__constructor()
next definition

return $container->get('overblog_graphql.resolver_resolver')->resolve(["AppGraphQLResolversUserResolver", array(0 => $args)]);

Maybe it helps you

so no need to load your resolver like this.

I removed this from my services configuration, but result is the same.
I use Symfony 4.0-Beta1 and bundle v0.10.0@dev

Finally I found mistake in my definition.

The final solution is

'@=resolver("App\\GraphQL\\Resolver\\UserResolver", [args])'

(define Resolver without "s" :man_facepalming: )

and resolver class I implement from definition Resolver interface.
This time all works as expected. Thanks

@Guikingone I added tests to cover no alias and no method for invokable classes using configuration. I confirm that It works correctly in #239

I'm closing this, reopen if needed :+1:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

paoloposo picture paoloposo  路  3Comments

AliceMakk picture AliceMakk  路  4Comments

VincentClair picture VincentClair  路  5Comments

rpander93 picture rpander93  路  3Comments

ruudk picture ruudk  路  5Comments