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

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

Closed
KaiGrassnick opened this issue Sep 6, 2024 · 3 comments
Labels

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

stale bot commented Nov 6, 2024

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Nov 6, 2024
@stale stale bot closed this as completed Nov 14, 2024
@NicoDPA
Copy link

NicoDPA commented Jan 29, 2025

Hi @KaiGrassnick, did you resolve this issue?

@belka-ew
Copy link

belka-ew commented May 5, 2025

Just for visibility, there is an open pull request even if the issue itself is closed: #6592. Probably it would be wise to open the issue as bug since the pull request is kind of stale, but the issue is confirmed.

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

3 participants