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

LinksHandler throwing: The class "\App\Entity\xxx" cannot be retrieved from "\App\ApiResource\xxx". When using stateOptions with stateOption entityClass and Collections - GraphQL Query #6590

Open
KaiGrassnick opened this issue Sep 6, 2024 · 0 comments

Comments

@KaiGrassnick
Copy link

KaiGrassnick commented Sep 6, 2024

API Platform version(s) affected: 3.3.12

Description
When converting from Entity ApiResource to DTOs with ApiResource and querying an API Resource which contains a OneToMany Relationship, an error is thrown by the LinksHandlerTrait which states:
The class "\App\Entity\xxx" cannot be retrieved from "\App\ApiResource\xxx" which is unexpected, as the Entity should not be retrieved from the DTO Class.
Note: This works fine using the Entity ApiResource.

Comparing the Processed Data inside the File: api/vendor/api-platform/core/src/Doctrine/Common/State/LinksHandlerTrait.php at Line 80 - 85 i could see, that the Handler is fed with resourceClass \App\Entity\xxx which is compared to \Api\Resource\xxx and obviously fails. Debugging the same Data when defining the API Resource above an Entity i could see, that \App\Entity\xxx was compared against \App\Entity\xxx which does obviously work.

How to reproduce

See my example Repository: https://github.com/KaiGrassnick/api-platform3-graphql-one-to-many-issue

Otherwise:

  1. Create an API Platform Project, add GraphQL Dependency (webonyx/graphql-php)
  2. Create 2 Entities and reference them One to Many
  3. Create 2 API Resources with stateOptions referring to the Entity ( example below )
  4. Run Query on the Resource which should contain the Array of the referenced Resources
{
  serverApiResources {
    edges {
      node {
        name
        ips {
          edges {
            node {
              ip
              public
            }
          }
        }
      }
    }
  }
}

Example API Resources:

#[ApiResource(
    graphQlOperations: [
        new Query(),
        new QueryCollection()
    ],
    provider: EntityClassDtoStateProvider::class,
    processor: EntityClassDtoStateProcessor::class,
    stateOptions: new Options(entityClass: ServerEntity::class),
)]
class ServerApiResource
{
    #[ApiProperty(readable: false, writable: false, identifier: true)]
    public ?int $id = null;

    public string $name;

    /**
     * @var IpApiResource[]
     */
    public array $ips = [];

    public function addIp(IpApiResource $ip): void
    {
        $this->ips[] = $ip;
    }

    public function removeIp(IpApiResource $ip): void
    {
        unset($this->ips[array_search($ip, $this->ips)]);
    }
}
#[ApiResource(
    graphQlOperations: [
        new Query(),
        new QueryCollection()
    ],
    provider: EntityClassDtoStateProvider::class,
    processor: EntityClassDtoStateProcessor::class,
    stateOptions: new Options(entityClass: IpEntity::class),
)]
class IpApiResource
{
    #[ApiProperty(readable: true, writable: false, identifier: true)]
    public ?int $id = null;

    public string $ip;

    public bool $public;
}

Possible Solution
I've looked into the LinksHandlerTrait File (api/vendor/api-platform/core/src/Doctrine/Common/State/LinksHandlerTrait.php), it seems that we could replace the $resourceClass ( which seems to be populated with the Entity Class ) with $context['resource_class']. If we do that, the query works just fine, but so far i have not checked any side effects regarding mutations etc.

Additional Context

Error Output:

{
  "errors": [
    {
      "message": "The class \"App\\Entity\\IpEntity\" cannot be retrieved from \"App\\ApiResource\\ServerApiResource\".",
      "locations": [
        {
          "line": 11,
          "column": 9
        }
      ],
      "path": [
        "serverApiResources",
        "edges",
        0,
        "node",
        "ips"
      ],
      "extensions": {
        "debugMessage": "The class \"App\\Entity\\IpEntity\" cannot be retrieved from \"App\\ApiResource\\ServerApiResource\".",
        "file": "/app/vendor/api-platform/core/src/Doctrine/Common/State/LinksHandlerTrait.php",
        "line": 88,
        "trace": [
          {
            "file": "/app/vendor/api-platform/core/src/Doctrine/Orm/State/LinksHandlerTrait.php",
            "line": 43,
            "call": "ApiPlatform\\Doctrine\\Orm\\State\\LinksHandler::getLinks('App\\Entity\\IpEntity', instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(14))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Doctrine/Orm/State/LinksHandler.php",
            "line": 35,
            "call": "ApiPlatform\\Doctrine\\Orm\\State\\LinksHandler::handle(instance of Doctrine\\ORM\\QueryBuilder, array(1), instance of ApiPlatform\\Doctrine\\Orm\\Util\\QueryNameGenerator, array(14), 'App\\Entity\\IpEntity', instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection)"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Doctrine/Orm/State/CollectionProvider.php",
            "line": 68,
            "call": "ApiPlatform\\Doctrine\\Orm\\State\\LinksHandler::handleLinks(instance of Doctrine\\ORM\\QueryBuilder, array(1), instance of ApiPlatform\\Doctrine\\Orm\\Util\\QueryNameGenerator, array(14))"
          },
          {
            "file": "/app/src/State/EntityClassDtoStateProvider.php",
            "line": 30,
            "call": "ApiPlatform\\Doctrine\\Orm\\State\\CollectionProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(1), array(13))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/State/CallableProvider.php",
            "line": 43,
            "call": "App\\State\\EntityClassDtoStateProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(1), array(13))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/GraphQl/State/Provider/ReadProvider.php",
            "line": 114,
            "call": "ApiPlatform\\State\\CallableProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(1), array(13))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Symfony/Security/State/AccessCheckerProvider.php",
            "line": 62,
            "call": "ApiPlatform\\GraphQl\\State\\Provider\\ReadProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(1), array(13))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/State/Provider/ParameterProvider.php",
            "line": 99,
            "call": "ApiPlatform\\Symfony\\Security\\State\\AccessCheckerProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(6))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/GraphQl/State/Provider/DenormalizeProvider.php",
            "line": 38,
            "call": "ApiPlatform\\State\\Provider\\ParameterProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(6))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Symfony/Security/State/AccessCheckerProvider.php",
            "line": 62,
            "call": "ApiPlatform\\GraphQl\\State\\Provider\\DenormalizeProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(5))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Symfony/Validator/State/ValidateProvider.php",
            "line": 32,
            "call": "ApiPlatform\\Symfony\\Security\\State\\AccessCheckerProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(5))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Symfony/Security/State/AccessCheckerProvider.php",
            "line": 62,
            "call": "ApiPlatform\\Symfony\\Validator\\State\\ValidateProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(5))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/GraphQl/State/Provider/ResolverProvider.php",
            "line": 36,
            "call": "ApiPlatform\\Symfony\\Security\\State\\AccessCheckerProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(5))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Symfony/Validator/State/ValidateProvider.php",
            "line": 32,
            "call": "ApiPlatform\\GraphQl\\State\\Provider\\ResolverProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(5))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/Symfony/Security/State/AccessCheckerProvider.php",
            "line": 62,
            "call": "ApiPlatform\\Symfony\\Validator\\State\\ValidateProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(5))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/GraphQl/Resolver/Factory/ResolverFactory.php",
            "line": 82,
            "call": "ApiPlatform\\Symfony\\Security\\State\\AccessCheckerProvider::provide(instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, array(0), array(5))"
          },
          {
            "file": "/app/vendor/api-platform/core/src/GraphQl/Resolver/Factory/ResolverFactory.php",
            "line": 66,
            "call": "ApiPlatform\\GraphQl\\Resolver\\Factory\\ResolverFactory::resolve(array(6), array(0), instance of GraphQL\\Type\\Definition\\ResolveInfo, 'App\\ApiResource\\ServerApiResource', instance of ApiPlatform\\Metadata\\GraphQl\\QueryCollection, null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 737,
            "call": "ApiPlatform\\GraphQl\\Resolver\\Factory\\ResolverFactory::ApiPlatform\\GraphQl\\Resolver\\Factory\\{closure}(array(6), array(0), null, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 653,
            "call": "GraphQL\\Executor\\ReferenceExecutor::resolveFieldValueOrError(instance of GraphQL\\Type\\Definition\\FieldDefinition, instance of GraphQL\\Language\\AST\\FieldNode, instance of Closure, array(6), instance of GraphQL\\Type\\Definition\\ResolveInfo, null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1361,
            "call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: ServerApiResource, array(6), instance of ArrayObject(1), 'ips', array(5), array(5), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1301,
            "call": "GraphQL\\Executor\\ReferenceExecutor::executeFields(GraphQLType: ServerApiResource, array(6), array(4), array(4), instance of ArrayObject(4), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1252,
            "call": "GraphQL\\Executor\\ReferenceExecutor::collectAndExecuteSubfields(GraphQLType: ServerApiResource, instance of ArrayObject(1), array(4), array(4), array(6), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 922,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeObjectValue(GraphQLType: ServerApiResource, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(4), array(4), array(6), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 777,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValue(GraphQLType: ServerApiResource, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(4), array(4), array(6), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 662,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValueCatchingError(GraphQLType: ServerApiResource, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(4), array(4), array(6), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1361,
            "call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: ServerApiResourceEdge, array(1), instance of ArrayObject(1), 'node', array(4), array(4), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1301,
            "call": "GraphQL\\Executor\\ReferenceExecutor::executeFields(GraphQLType: ServerApiResourceEdge, array(1), array(3), array(3), instance of ArrayObject(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1252,
            "call": "GraphQL\\Executor\\ReferenceExecutor::collectAndExecuteSubfields(GraphQLType: ServerApiResourceEdge, instance of ArrayObject(1), array(3), array(3), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 922,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeObjectValue(GraphQLType: ServerApiResourceEdge, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(3), array(3), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 777,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValue(GraphQLType: ServerApiResourceEdge, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(3), array(3), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1019,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValueCatchingError(GraphQLType: ServerApiResourceEdge, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(3), array(3), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 900,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeListValue(GraphQLType: [ServerApiResourceEdge], instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(2), array(2), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 777,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValue(GraphQLType: [ServerApiResourceEdge], instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(2), array(2), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 662,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValueCatchingError(GraphQLType: [ServerApiResourceEdge], instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(2), array(2), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1361,
            "call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: ServerApiResourceCursorConnection, array(1), instance of ArrayObject(1), 'edges', array(2), array(2), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1301,
            "call": "GraphQL\\Executor\\ReferenceExecutor::executeFields(GraphQLType: ServerApiResourceCursorConnection, array(1), array(1), array(1), instance of ArrayObject(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1252,
            "call": "GraphQL\\Executor\\ReferenceExecutor::collectAndExecuteSubfields(GraphQLType: ServerApiResourceCursorConnection, instance of ArrayObject(1), array(1), array(1), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 922,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeObjectValue(GraphQLType: ServerApiResourceCursorConnection, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(1), array(1), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 777,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValue(GraphQLType: ServerApiResourceCursorConnection, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(1), array(1), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 662,
            "call": "GraphQL\\Executor\\ReferenceExecutor::completeValueCatchingError(GraphQLType: ServerApiResourceCursorConnection, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(1), array(1), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 1361,
            "call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: Query, null, instance of ArrayObject(1), 'serverApiResources', array(1), array(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 299,
            "call": "GraphQL\\Executor\\ReferenceExecutor::executeFields(GraphQLType: Query, null, array(0), array(0), instance of ArrayObject(1), null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",
            "line": 237,
            "call": "GraphQL\\Executor\\ReferenceExecutor::executeOperation(instance of GraphQL\\Language\\AST\\OperationDefinitionNode, null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/Executor/Executor.php",
            "line": 159,
            "call": "GraphQL\\Executor\\ReferenceExecutor::doExecute()"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/GraphQL.php",
            "line": 162,
            "call": "GraphQL\\Executor\\Executor::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, null, array(0), null, null)"
          },
          {
            "file": "/app/vendor/webonyx/graphql-php/src/GraphQL.php",
            "line": 96,
            "call": "GraphQL\\GraphQL::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, '{\n  serverApiResources {\n    edges {\n      node {\n        name\n        memory\n        architecture {\n          id\n          name\n        }\n        ips {\n          edges {\n            node {\n              ip\n              public\n            }\n          }\n        }\n      }\n    }\n  }\n}', null, null, array(0), null, null, null)"
          },
          {
            "file": "/app/vendor/api-platform/core/src/GraphQl/Executor.php",
            "line": 43,
            "call": "GraphQL\\GraphQL::executeQuery(instance of GraphQL\\Type\\Schema, '{\n  serverApiResources {\n    edges {\n      node {\n        name\n        memory\n        architecture {\n          id\n          name\n        }\n        ips {\n          edges {\n            node {\n              ip\n              public\n            }\n          }\n        }\n      }\n    }\n  }\n}', null, null, array(0), null, null, null)"
          },
          {
            "file": "/app/vendor/api-platform/core/src/GraphQl/Action/EntrypointAction.php",
            "line": 79,
            "call": "ApiPlatform\\GraphQl\\Executor::executeQuery(instance of GraphQL\\Type\\Schema, '{\n  serverApiResources {\n    edges {\n      node {\n        name\n        memory\n        architecture {\n          id\n          name\n        }\n        ips {\n          edges {\n            node {\n              ip\n              public\n            }\n          }\n        }\n      }\n    }\n  }\n}', null, null, array(0), null)"
          },
          {
            "file": "/app/vendor/symfony/http-kernel/HttpKernel.php",
            "line": 181,
            "call": "ApiPlatform\\GraphQl\\Action\\EntrypointAction::__invoke(instance of Symfony\\Component\\HttpFoundation\\Request)"
          },
          {
            "file": "/app/vendor/symfony/http-kernel/HttpKernel.php",
            "line": 76,
            "call": "Symfony\\Component\\HttpKernel\\HttpKernel::handleRaw(instance of Symfony\\Component\\HttpFoundation\\Request, 1)"
          },
          {
            "file": "/app/vendor/symfony/http-kernel/Kernel.php",
            "line": 197,
            "call": "Symfony\\Component\\HttpKernel\\HttpKernel::handle(instance of Symfony\\Component\\HttpFoundation\\Request, 1, true)"
          },
          {
            "file": "/app/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php",
            "line": 35,
            "call": "Symfony\\Component\\HttpKernel\\Kernel::handle(instance of Symfony\\Component\\HttpFoundation\\Request)"
          },
          {
            "file": "/app/vendor/autoload_runtime.php",
            "line": 29,
            "call": "Symfony\\Component\\Runtime\\Runner\\Symfony\\HttpKernelRunner::run()"
          },
          {
            "file": "/app/public/index.php",
            "line": 5,
            "function": "require_once('/app/vendor/autoload_runtime.php')"
          }
        ]
      }
    }
  ],
  "data": {
    "serverApiResources": {
      "edges": [
        {
          "node": {
            "name": "server1",
            "memory": 1024,
            "architecture": {
              "id": "/architecture_api_resources/1",
              "name": "x86"
            },
            "ips": null
          }
        }
      ]
    }
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant