Skip to content

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

Closed
@KaiGrassnick

Description

@KaiGrassnick

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
          }
        }
      ]
    }
  }

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