overblog / GraphQLBundle

This bundle provides tools to build a complete GraphQL API server in your Symfony App.
MIT License
783 stars 221 forks source link

The path "overblog_graphql_types.RootQuery._object_config.fields" should have at least 1 element(s) defined. #1162

Closed alexhampu closed 6 months ago

alexhampu commented 7 months ago
Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? no
Version/Branch 1.1.0

Hello, I am trying to set up this bundle with Symfony 7 by following the docs, however it doesn't work, and I cannot seem to figure out why.

My config:

namespace App\Core\Presentation\GraphQL\Types;

use Overblog\GraphQLBundle\Annotation\Type;

#[Type]
class RootMutation
{
}
namespace App\Core\Presentation\GraphQL\Types;

use Overblog\GraphQLBundle\Annotation\Type;

#[Type]
class RootQuery
{
}

config/packages/graphql.yaml:

overblog_graphql:
  definitions:
    # disable listener the bundle out of box classLoader
    use_classloader_listener: false

    # change to "false" to disable auto compilation.
    # To generate types manually, see "graphql:compile" command.
    auto_compile: true

    # change classes cache dir (recommends using a directory that will be committed)
    cache_dir: "%kernel.project_dir%/graphql"

    schema:
      query: RootQuery
      mutation: RootMutation

    mappings:
      types:
        - type: attribute
          dir: "%kernel.project_dir%/app/*/Core/GraphQL/Types"
          suffix: ~

        - type: attribute
          dir: "%kernel.project_dir%/app/*/Presentation/GraphQL/Types"
          suffix: ~

when@dev:
  overblog_graphql:
    definitions:
      show_debug_info: true
      config_validation: "%kernel.debug%"

config/services.yaml:

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
  graphql.domain: "%env(GRAPHQL_DOMAIN)%"
  tenant.domain_suffix: "%env(TENANT_DOMAIN_SUFFIX)%"
  tenant.domain_separator: "%env(TENANT_DOMAIN_SEPARATOR)%"

services:
  # default configuration for services in *this* file
  _defaults:
    autowire: true      # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

  # makes classes in app/ available to be used as services
  # this creates a service per class whose id is the fully-qualified class name

  App\:
    resource: '../app/'
    public: true
    exclude:
      - '../app/Kernel.php'
      - '../app/*/Domain/ValueObjects'
      - '../app/*/Domain/Entities'

I also have an additional type, a mutation mutation and a query defined like this:

namespace App\Tenant\Presentation\GraphQL\Mutations;

use Overblog\GraphQLBundle\Annotation\Mutation;
use Overblog\GraphQLBundle\Annotation\Provider;
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface;

#[Provider]
class UpdateHostnameMutation implements MutationInterface, AliasedInterface
{
    #[Mutation]
    public function __invoke(Argument $input): array
    {
        return [
            'status' => 'done'
        ];
    }

    #[\Override] public static function getAliases(): array
    {
        return ['__invoke' => 'UpdateHostname'];
    }
}
namespace App\Tenant\Presentation\GraphQL\Queries;

use GraphQL\Type\Definition\ResolveInfo;
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
use Overblog\GraphQLBundle\Definition\Resolver\QueryInterface;

class GetTenantQuery implements QueryInterface, AliasedInterface
{
    public function __invoke(ResolveInfo $info, $name): string
    {
        return 'ok';
    }

    #[\Override] public static function getAliases(): array
    {
        return ['__invoke' => 'getTenant'];
    }
}
namespace App\Tenant\Presentation\GraphQL\Types;

use Overblog\GraphQLBundle\Annotation\Field;
use Overblog\GraphQLBundle\Annotation\Type;

#[Type]
class Tenant
{
    #[Field]
    public string $id;

    #[Field]
    public string $hostname;
}

I always get the error:

The path "overblog_graphql_types.RootQuery._object_config.fields" should have at least 1 element(s) defined.

Because it doesn't detect the other queries or mutations.

If I add some custom fields to the RootQuery and RootMutation and run:

bin/console graphql:compile

I get all the defined types, and when looking into RootQueryType and RootMutationType only the custom fields are defined into it.

What am I doing wrong that causes this issue?

alexhampu commented 7 months ago

I am aware of the following issues:

Vincz commented 7 months ago

Hi @alexhampu It appears you don't have any query provider. You need to have a #[GQL\Query] on a #[GQL\Provider].
Your GetTenantQuery seems to be a resolver but it doesn't seem you have configured any field on your RootQuery.

alexhampu commented 7 months ago

Hello @Vincz, Thank you for taking your time to help. Even if I add Provider and Query attributes to the GetTenantQuery class, it still throws the error that I don't have any fields in RootQuery. In the documentation, it says to have an empty RootQuery and RootMutation.

alexhampu commented 7 months ago

It looks like the providers are not discovered, even though I can see them as services in php bin/console debug:container as well as bin/console graphql:debug when I extend the QueryInterface for example.

Vincz commented 6 months ago

Hi @alexhampu! Any update about this issue?

alexhampu commented 6 months ago

Hello @Vincz , this issue still happens and I have found no fix for it. I have also tried it on a fresh project.

Vincz commented 6 months ago

@alexhampu I think you mixed things up.
When using the attribute, #[GQL\Provider] is placed on a class that should have a corresponding public service with the name of the class. The class itself must be located under a mapped directory (in your example, you are mapping %kernel.project_dir%/app/*/Core/GraphQL/Types and %kernel.project_dir%/app/*/Presentation/GraphQL/Types but none of them seem to include your provider namespace.

When the #[GQL\Provider] annotation is used, the system will check in the class for #[GQL\Query] and #[GQL\Mutation] and add them by default on the root Query or root Mutation.

The QueryInterface, AliasedInterface, MutationInterface are a different story. There are just used to declare resolvers that can later be used with @=query for example. But it's not related to attributes or annotations.

alexhampu commented 6 months ago

First, let me thank you @Vincz for coming back to try to help me.

I have tried with the following setup:

config/services.yaml

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
  graphql.domain: "%env(GRAPHQL_DOMAIN)%"
  tenant.domain_suffix: "%env(TENANT_DOMAIN_SUFFIX)%"
  tenant.domain_separator: "%env(TENANT_DOMAIN_SEPARATOR)%"

services:
  # default configuration for services in *this* file
  _defaults:
    autowire: true      # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

  # makes classes in app/ available to be used as services
  # this creates a service per class whose id is the fully-qualified class name

  App\Core\:
    resource: '../app/Core/*'
  App\Tenant\:
    resource: '../app/Tenant/*'

config/packages/graphql.yaml

overblog_graphql:
  definitions:
    # disable listener the bundle out of box classLoader
    use_classloader_listener: false

    # change to "false" to disable auto compilation.
    # To generate types manually, see "graphql:compile" command.
    auto_compile: false

    # change classes cache dir (recommends using a directory that will be committed)
    cache_dir: "%kernel.project_dir%/graphql"

    schema:
      query: RootQuery

    mappings:
      types:
        - type: attribute
          dir: "%kernel.project_dir%/app/*/Core/GraphQL/Types"
          suffix: ~
        - type: attribute
          dir: "%kernel.project_dir%/app/*/Presentation/GraphQL/Types"
          suffix: ~

when@dev:
  overblog_graphql:
    definitions:
      show_debug_info: true
      config_validation: "%kernel.debug%"
<?php

namespace App\Core\Presentation\GraphQL\Types;

use Overblog\GraphQLBundle\Annotation\Type;

#[Type]
class RootQuery
{
}
<?php

namespace App\Tenant\Presentation\GraphQL\Queries;

use App\Tenant\Presentation\GraphQL\Types\Tenant;
use Overblog\GraphQLBundle\Annotation\Provider;
use Overblog\GraphQLBundle\Annotation\Query;
use Overblog\GraphQLBundle\Definition\Resolver\QueryInterface;

#[Provider]
class GetTenantQuery
{
    #[Query]
    public function tenant(): Tenant
    {
        $tenant = new Tenant();
        $tenant->id = 'random-uuid';
        $tenant->hostname = 'tst';

        return $tenant;
    }

    #[Query]
    public function getUsers(): array
    {
        return [];
    }
}
<?php

namespace App\Tenant\Presentation\GraphQL\Types;

use Overblog\GraphQLBundle\Annotation\Field;
use Overblog\GraphQLBundle\Annotation\Type;

#[Type(name: 'Tenant')]
class Tenant
{
    #[Field]
    public string $id;

    #[Field]
    public string $hostname;
}

So I have a class with #[GQL\Provider] which is GetTenantQuery, in there I have 2 methods with #[GQL\Query]. I also have the default RootQuery, but I still get:

The path "overblog_graphql_types.RootQuery._object_config.fields" should have at least 1 element(s) defined.

I still don't understand what I'm doing wrong.

Vincz commented 6 months ago

@alexhampu You need to include the providers namespace into your mapping.

overblog_graphql:
    mappings:
      types:
        - type: attribute
          dir: "%kernel.project_dir%/app/*/Presentation/GraphQL/Queries"
          suffix: ~
alexhampu commented 6 months ago

Finally it's working, thank you @Vincz. I followed the docs and either I missed this information, or it wasn't here. Thank you very much for your time!