Description
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:
- Create an API Platform Project, add GraphQL Dependency (
webonyx/graphql-php
) - Create 2 Entities and reference them One to Many
- Create 2 API Resources with
stateOptions
referring to the Entity ( example below ) - 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
}
}
]
}
}