diff --git a/CHANGELOG.md b/CHANGELOG.md
index 749279a1155..0643d4d3f8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -108,6 +108,13 @@ api_platform:
form: ['multipart/form-data']
```
+## v3.2.20
+
+### Bug fixes
+
+* [90c9fb31a](https://github.com/api-platform/core/commit/90c9fb31a322a2c7891fbbafb75d60b09fd67772) fix(symfony): register api_error route (#6281)
+* [d061c3811](https://github.com/api-platform/core/commit/d061c381120b858c471157dbdccbb5084a39fb04) fix(graphql): increment graphql normalizer priority (#6283)
+
## v3.2.19
### Bug fixes
diff --git a/composer.json b/composer.json
index 0cc729fe56d..5f1c863939c 100644
--- a/composer.json
+++ b/composer.json
@@ -108,6 +108,7 @@
"doctrine/orm": "<2.14.0",
"doctrine/mongodb-odm": "<2.4",
"doctrine/persistence": "<1.3",
+ "symfony/framework-bundle": "6.4.6 || 7.0.6",
"symfony/var-exporter": "<6.1.1",
"phpunit/phpunit": "<9.5",
"phpspec/prophecy": "<1.15",
diff --git a/features/graphql/query.feature b/features/graphql/query.feature
index 2021d9039a7..cbf2bdfb892 100644
--- a/features/graphql/query.feature
+++ b/features/graphql/query.feature
@@ -22,7 +22,7 @@ Feature: GraphQL query support
@createSchema
Scenario: Retrieve an item with different relations to the same resource
- Given there are 2 multiRelationsDummy objects having each a manyToOneRelation, 2 manyToManyRelations, 3 oneToManyRelations and 4 embeddedRelations
+ Given there are 2 multiRelationsDummy objects having each 1 manyToOneRelation, 2 manyToManyRelations, 3 oneToManyRelations and 4 embeddedRelations
When I send the following GraphQL request:
"""
{
@@ -33,6 +33,10 @@ Feature: GraphQL query support
id
name
}
+ manyToOneResolveRelation {
+ id
+ name
+ }
manyToManyRelations {
edges{
node {
@@ -70,7 +74,7 @@ Feature: GraphQL query support
@createSchema
Scenario: Retrieve embedded collections
- Given there are 2 multiRelationsDummy objects having each a manyToOneRelation, 2 manyToManyRelations, 3 oneToManyRelations and 4 embeddedRelations
+ Given there are 2 multiRelationsDummy objects having each 1 manyToOneRelation, 2 manyToManyRelations, 3 oneToManyRelations and 4 embeddedRelations
When I send the following GraphQL request:
"""
{
@@ -81,6 +85,10 @@ Feature: GraphQL query support
id
name
}
+ manyToOneResolveRelation {
+ id
+ name
+ }
manyToManyRelations {
edges{
node {
@@ -113,10 +121,13 @@ Feature: GraphQL query support
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
+ And the JSON node "errors" should not exist
And the JSON node "data.multiRelationsDummy.id" should be equal to "/multi_relations_dummies/2"
And the JSON node "data.multiRelationsDummy.name" should be equal to "Dummy #2"
And the JSON node "data.multiRelationsDummy.manyToOneRelation.id" should not be null
And the JSON node "data.multiRelationsDummy.manyToOneRelation.name" should be equal to "RelatedManyToOneDummy #2"
+ And the JSON node "data.multiRelationsDummy.manyToOneResolveRelation.id" should not be null
+ And the JSON node "data.multiRelationsDummy.manyToOneResolveRelation.name" should be equal to "RelatedManyToOneResolveDummy #2"
And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges" should have 2 element
And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges[1].node.id" should not be null
And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges[0].node.name" should match "#RelatedManyToManyDummy(1|2)2#"
@@ -135,6 +146,65 @@ Feature: GraphQL query support
And the JSON node "data.multiRelationsDummy.nestedPaginatedCollection.edges[2].node.name" should be equal to "NestedPaginatedDummy3"
And the JSON node "data.multiRelationsDummy.nestedPaginatedCollection.edges[3].node.name" should be equal to "NestedPaginatedDummy4"
+ @createSchema
+ Scenario: Retrieve an item with different relations (all unset)
+ Given there are 2 multiRelationsDummy objects having each 0 manyToOneRelation, 0 manyToManyRelations, 0 oneToManyRelations and 0 embeddedRelations
+ When I send the following GraphQL request:
+ """
+ {
+ multiRelationsDummy(id: "/multi_relations_dummies/2") {
+ id
+ name
+ manyToOneRelation {
+ id
+ name
+ }
+ manyToOneResolveRelation {
+ id
+ name
+ }
+ manyToManyRelations {
+ edges{
+ node {
+ id
+ name
+ }
+ }
+ }
+ oneToManyRelations {
+ edges{
+ node {
+ id
+ name
+ }
+ }
+ }
+ nestedCollection {
+ name
+ }
+ nestedPaginatedCollection {
+ edges{
+ node {
+ name
+ }
+ }
+ }
+ }
+ }
+ """
+ Then the response status code should be 200
+ And the response should be in JSON
+ And the header "Content-Type" should be equal to "application/json"
+ And the JSON node "errors" should not exist
+ And the JSON node "data.multiRelationsDummy.id" should be equal to "/multi_relations_dummies/2"
+ And the JSON node "data.multiRelationsDummy.name" should be equal to "Dummy #2"
+ And the JSON node "data.multiRelationsDummy.manyToOneRelation" should be null
+ And the JSON node "data.multiRelationsDummy.manyToOneResolveRelation" should be null
+ And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges" should have 0 element
+ And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges" should have 0 element
+ And the JSON node "data.multiRelationsDummy.nestedCollection" should have 0 element
+ And the JSON node "data.multiRelationsDummy.nestedPaginatedCollection.edges" should have 0 element
+
@createSchema @!mongodb
Scenario: Retrieve an item with child relation to the same resource
Given there are tree dummies
diff --git a/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php b/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php
index a25e477c1f2..5f7e31f2af0 100644
--- a/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php
+++ b/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php
@@ -20,6 +20,7 @@
use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface;
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Util\CloneTrait;
use ApiPlatform\State\Pagination\ArrayPaginator;
use GraphQL\Type\Definition\ResolveInfo;
@@ -40,7 +41,7 @@ public function __construct(private readonly ReadStageInterface $readStage, priv
{
}
- public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable
+ public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null, ?PropertyMetadataFactoryInterface $propertyMetadataFactory = null): callable
{
return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation): ?array {
// If authorization has failed for a relation field (e.g. via ApiProperty security), the field is not present in the source: null can be returned directly to ensure the collection isn't in the response.
diff --git a/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php
index 999ea9a51ae..ffe1e3d04c2 100644
--- a/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php
+++ b/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php
@@ -24,6 +24,7 @@
use ApiPlatform\GraphQl\Resolver\Stage\WriteStageInterface;
use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\GraphQl\Operation;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Metadata\Util\CloneTrait;
use GraphQL\Type\Definition\ResolveInfo;
@@ -44,7 +45,7 @@ public function __construct(private readonly ReadStageInterface $readStage, priv
{
}
- public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable
+ public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null, ?PropertyMetadataFactoryInterface $propertyMetadataFactory = null): callable
{
return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation): ?array {
if (null === $resourceClass || null === $operation) {
diff --git a/src/GraphQl/Resolver/Factory/ItemResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemResolverFactory.php
index a1652b9bca7..45820aaeed4 100644
--- a/src/GraphQl/Resolver/Factory/ItemResolverFactory.php
+++ b/src/GraphQl/Resolver/Factory/ItemResolverFactory.php
@@ -20,6 +20,7 @@
use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface;
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Metadata\Util\CloneTrait;
use GraphQL\Type\Definition\ResolveInfo;
@@ -41,7 +42,7 @@ public function __construct(private readonly ReadStageInterface $readStage, priv
{
}
- public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable
+ public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null, ?PropertyMetadataFactoryInterface $propertyMetadataFactory = null): callable
{
return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation) {
// Data already fetched and normalized (field or nested resource)
diff --git a/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php
index 0335a6ac232..b182ba528ff 100644
--- a/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php
+++ b/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php
@@ -19,6 +19,7 @@
use ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface;
use ApiPlatform\GraphQl\Subscription\SubscriptionManagerInterface;
use ApiPlatform\Metadata\GraphQl\Operation;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Metadata\Util\CloneTrait;
use GraphQL\Type\Definition\ResolveInfo;
@@ -37,7 +38,7 @@ public function __construct(private readonly ReadStageInterface $readStage, priv
{
}
- public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable
+ public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null, ?PropertyMetadataFactoryInterface $propertyMetadataFactory = null): callable
{
return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation): ?array {
if (null === $resourceClass || null === $operation) {
diff --git a/src/GraphQl/Resolver/Factory/ResolverFactory.php b/src/GraphQl/Resolver/Factory/ResolverFactory.php
index 64faed799dc..6577c95521d 100644
--- a/src/GraphQl/Resolver/Factory/ResolverFactory.php
+++ b/src/GraphQl/Resolver/Factory/ResolverFactory.php
@@ -17,6 +17,7 @@
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\State\Pagination\ArrayPaginator;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\State\ProviderInterface;
@@ -30,21 +31,30 @@ public function __construct(
) {
}
- public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable
+ public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null, ?PropertyMetadataFactoryInterface $propertyMetadataFactory = null): callable
{
- return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation) {
- // Data already fetched and normalized (field or nested resource)
- if ($body = $source[$info->fieldName] ?? null) {
+ return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation, $propertyMetadataFactory) {
+ if (\array_key_exists($info->fieldName, $source ?? [])) {
+ $body = $source[$info->fieldName];
+
// special treatment for nested resources without a resolver/provider
if ($operation instanceof Query && $operation->getNested() && !$operation->getResolver() && (!$operation->getProvider() || NoopProvider::class === $operation->getProvider())) {
- return $this->resolve($source, $args, $info, $rootClass, $operation, new ArrayPaginator($body, 0, \count($body)));
+ return \is_array($body) ? $this->resolve(
+ $source,
+ $args,
+ $info,
+ $rootClass,
+ $operation,
+ new ArrayPaginator($body, 0, \count($body))
+ ) : $body;
}
- return $body;
- }
-
- if (null === $resourceClass && \array_key_exists($info->fieldName, $source ?? [])) {
- return $body;
+ $propertyMetadata = $rootClass ? $propertyMetadataFactory?->create($rootClass, $info->fieldName) : null;
+ $type = $propertyMetadata?->getBuiltinTypes()[0] ?? null;
+ // Data already fetched and normalized (field or nested resource)
+ if ($body || null === $resourceClass || ($type && !$type->isCollection())) {
+ return $body;
+ }
}
// If authorization has failed for a relation field (e.g. via ApiProperty security), the field is not present in the source: null can be returned directly to ensure the collection isn't in the response.
diff --git a/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php b/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php
index c65e8f16682..4fced141822 100644
--- a/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php
+++ b/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php
@@ -14,6 +14,7 @@
namespace ApiPlatform\GraphQl\Resolver\Factory;
use ApiPlatform\Metadata\GraphQl\Operation;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
/**
* Builds a GraphQL resolver.
@@ -22,5 +23,5 @@
*/
interface ResolverFactoryInterface
{
- public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable;
+ public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null, ?PropertyMetadataFactoryInterface $propertyMetadataFactory = null): callable;
}
diff --git a/src/GraphQl/Tests/Resolver/Factory/ResolverFactoryTest.php b/src/GraphQl/Tests/Resolver/Factory/ResolverFactoryTest.php
index 574f8d1ef83..ee0be4aef60 100644
--- a/src/GraphQl/Tests/Resolver/Factory/ResolverFactoryTest.php
+++ b/src/GraphQl/Tests/Resolver/Factory/ResolverFactoryTest.php
@@ -14,9 +14,11 @@
namespace ApiPlatform\GraphQl\Tests\Resolver\Factory;
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactory;
+use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\State\ProviderInterface;
use GraphQL\Type\Definition\ResolveInfo;
@@ -38,11 +40,13 @@ public function testGraphQlResolver(?string $resourceClass = null, ?string $root
$provider->expects($this->once())->method('provide')->with($providedOperation ?: $operation, [], $context)->willReturn($body);
$processor = $this->createMock(ProcessorInterface::class);
$processor->expects($this->once())->method('process')->with($body, $processedOperation ?: $operation, [], $context)->willReturn($returnValue);
+ $propertyMetadataFactory = $this->createMock(PropertyMetadataFactoryInterface::class);
+ $propertyMetadataFactory->expects($this->once())->method('create')->with($rootClass, 'test')->willReturn(new ApiProperty(schema: ['type' => 'array']));
$resolveInfo = $this->createMock(ResolveInfo::class);
$resolveInfo->fieldName = 'test';
$resolverFactory = new ResolverFactory($provider, $processor);
- $this->assertEquals($resolverFactory->__invoke($resourceClass, $rootClass, $operation)(['test' => null], [], [], $resolveInfo), $returnValue);
+ $this->assertEquals($resolverFactory->__invoke($resourceClass, $rootClass, $operation, $propertyMetadataFactory)(['test' => null], [], [], $resolveInfo), $returnValue);
}
public static function graphQlQueries(): array
diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php
index f708c6b9f84..0d1042e48ea 100644
--- a/src/GraphQl/Type/FieldsBuilder.php
+++ b/src/GraphQl/Type/FieldsBuilder.php
@@ -458,7 +458,7 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
if ($isStandardGraphqlType || $input) {
$resolve = null;
} else {
- $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resourceOperation);
+ $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resourceOperation, $this->propertyMetadataFactory);
}
} else {
if ($isStandardGraphqlType || $input) {
diff --git a/src/Symfony/Bundle/Resources/config/graphql.xml b/src/Symfony/Bundle/Resources/config/graphql.xml
index 9edae7441f5..14bf60b8405 100644
--- a/src/Symfony/Bundle/Resources/config/graphql.xml
+++ b/src/Symfony/Bundle/Resources/config/graphql.xml
@@ -218,7 +218,7 @@
-
+
diff --git a/src/Symfony/Bundle/Resources/config/routing/api.xml b/src/Symfony/Bundle/Resources/config/routing/api.xml
index e4524f69a2c..9eb63d600fc 100644
--- a/src/Symfony/Bundle/Resources/config/routing/api.xml
+++ b/src/Symfony/Bundle/Resources/config/routing/api.xml
@@ -13,16 +13,4 @@
index
-
-
- api_platform.action.not_exposed
- 500
-
- \d+
-
-
-
- api_platform.action.not_exposed
-
-
diff --git a/src/Symfony/Bundle/Resources/config/routing/errors.xml b/src/Symfony/Bundle/Resources/config/routing/errors.xml
new file mode 100644
index 00000000000..574515ce305
--- /dev/null
+++ b/src/Symfony/Bundle/Resources/config/routing/errors.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ api_platform.action.not_exposed
+ 500
+
+ \d+
+
+
+
+ api_platform.action.not_exposed
+
+
diff --git a/src/Symfony/GraphQl/Resolver/Factory/DataCollectorResolverFactory.php b/src/Symfony/GraphQl/Resolver/Factory/DataCollectorResolverFactory.php
index 9091ab3dc9e..72bf61223cc 100644
--- a/src/Symfony/GraphQl/Resolver/Factory/DataCollectorResolverFactory.php
+++ b/src/Symfony/GraphQl/Resolver/Factory/DataCollectorResolverFactory.php
@@ -15,6 +15,7 @@
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface;
use ApiPlatform\Metadata\GraphQl\Operation;
+use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use GraphQL\Type\Definition\ResolveInfo;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -24,7 +25,7 @@ public function __construct(private readonly ResolverFactoryInterface $resolverF
{
}
- public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable
+ public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null, ?PropertyMetadataFactoryInterface $propertyMetadataFactory = null): callable
{
return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation) {
if ($this->requestStack && null !== $request = $this->requestStack->getCurrentRequest()) {
diff --git a/src/Symfony/Routing/ApiLoader.php b/src/Symfony/Routing/ApiLoader.php
index 0d30198622b..832c068a78f 100644
--- a/src/Symfony/Routing/ApiLoader.php
+++ b/src/Symfony/Routing/ApiLoader.php
@@ -125,6 +125,7 @@ public function supports(mixed $resource, ?string $type = null): bool
private function loadExternalFiles(RouteCollection $routeCollection): void
{
$routeCollection->addCollection($this->fileLoader->load('genid.xml'));
+ $routeCollection->addCollection($this->fileLoader->load('errors.xml'));
if ($this->entrypointEnabled) {
$routeCollection->addCollection($this->fileLoader->load('api.xml'));
diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php
index d27b1b219e1..8894e737d37 100644
--- a/tests/Behat/DoctrineContext.php
+++ b/tests/Behat/DoctrineContext.php
@@ -69,6 +69,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Document\MultiRelationsNested as MultiRelationsNestedDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\MultiRelationsNestedPaginated as MultiRelationsNestedPaginatedDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\MultiRelationsRelatedDummy as MultiRelationsRelatedDummyDocument;
+use ApiPlatform\Tests\Fixtures\TestBundle\Document\MultiRelationsResolveDummy as MultiRelationsResolveDummyDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\MusicGroup as MusicGroupDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\NetworkPathDummy as NetworkPathDummyDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\NetworkPathRelationDummy as NetworkPathRelationDummyDocument;
@@ -165,6 +166,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsNested;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsNestedPaginated;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsRelatedDummy;
+use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsResolveDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MusicGroup;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\NetworkPathDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\NetworkPathRelationDummy;
@@ -809,17 +811,24 @@ public function thereAreDummyObjectsWithRelatedDummies(int $nb, int $nbrelated):
}
/**
- * @Given there are :nb multiRelationsDummy objects having each a manyToOneRelation, :nbmtmr manyToManyRelations, :nbotmr oneToManyRelations and :nber embeddedRelations
+ * @Given there are :nb multiRelationsDummy objects having each :nbmtor manyToOneRelation, :nbmtmr manyToManyRelations, :nbotmr oneToManyRelations and :nber embeddedRelations
*/
- public function thereAreMultiRelationsDummyObjectsHavingEachAManyToOneRelationManyToManyRelationsOneToManyRelationsAndEmbeddedRelations(int $nb, int $nbmtmr, int $nbotmr, int $nber): void
+ public function thereAreMultiRelationsDummyObjectsHavingEachAManyToOneRelationManyToManyRelationsOneToManyRelationsAndEmbeddedRelations(int $nb, int $nbmtor, int $nbmtmr, int $nbotmr, int $nber): void
{
for ($i = 1; $i <= $nb; ++$i) {
$relatedDummy = $this->buildMultiRelationsRelatedDummy();
$relatedDummy->name = 'RelatedManyToOneDummy #'.$i;
+ $resolveDummy = $this->buildMultiRelationsResolveDummy();
+ $resolveDummy->name = 'RelatedManyToOneResolveDummy #'.$i;
+
$dummy = $this->buildMultiRelationsDummy();
$dummy->name = 'Dummy #'.$i;
- $dummy->setManyToOneRelation($relatedDummy);
+
+ if ($nbmtor) {
+ $dummy->setManyToOneRelation($relatedDummy);
+ $dummy->setManyToOneResolveRelation($resolveDummy);
+ }
for ($j = 1; $j <= $nbmtmr; ++$j) {
$manyToManyItem = $this->buildMultiRelationsRelatedDummy();
@@ -855,6 +864,7 @@ public function thereAreMultiRelationsDummyObjectsHavingEachAManyToOneRelationMa
$dummy->setNestedPaginatedCollection($nestedPaginated);
$this->manager->persist($relatedDummy);
+ $this->manager->persist($resolveDummy);
$this->manager->persist($dummy);
}
$this->manager->flush();
@@ -2668,6 +2678,11 @@ private function buildMultiRelationsNestedPaginated(): MultiRelationsNestedPagin
return $this->isOrm() ? new MultiRelationsNestedPaginated() : new MultiRelationsNestedPaginatedDocument();
}
+ private function buildMultiRelationsResolveDummy(): MultiRelationsResolveDummy|MultiRelationsResolveDummyDocument
+ {
+ return $this->isOrm() ? new MultiRelationsResolveDummy() : new MultiRelationsResolveDummyDocument();
+ }
+
private function buildMusicGroup(): MusicGroup|MusicGroupDocument
{
return $this->isOrm() ? new MusicGroup() : new MusicGroupDocument();
diff --git a/tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php b/tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php
index 19733044e50..fbddd76ccda 100644
--- a/tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php
+++ b/tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php
@@ -38,6 +38,9 @@ class MultiRelationsDummy
#[ODM\ReferenceOne(targetDocument: MultiRelationsRelatedDummy::class, storeAs: 'id', nullable: true)]
public ?MultiRelationsRelatedDummy $manyToOneRelation = null;
+ #[ODM\ReferenceOne(targetDocument: MultiRelationsResolveDummy::class, storeAs: 'id', nullable: true)]
+ public ?MultiRelationsResolveDummy $manyToOneResolveRelation = null;
+
/** @var Collection */
#[ODM\ReferenceMany(targetDocument: MultiRelationsRelatedDummy::class, storeAs: 'id', nullable: true)]
public Collection $manyToManyRelations;
@@ -77,6 +80,18 @@ public function setManyToOneRelation(?MultiRelationsRelatedDummy $relatedMultiUs
$this->manyToOneRelation = $relatedMultiUsedDummy;
}
+ public function getManyToOneResolveRelation(): ?MultiRelationsResolveDummy
+ {
+ return $this->manyToOneResolveRelation;
+ }
+
+ public function setManyToOneResolveRelation(?MultiRelationsResolveDummy $manyToOneResolveRelation): self
+ {
+ $this->manyToOneResolveRelation = $manyToOneResolveRelation;
+
+ return $this;
+ }
+
public function addManyToManyRelation(MultiRelationsRelatedDummy $relatedMultiUsedDummy): void
{
$this->manyToManyRelations->add($relatedMultiUsedDummy);
diff --git a/tests/Fixtures/TestBundle/Document/MultiRelationsResolveDummy.php b/tests/Fixtures/TestBundle/Document/MultiRelationsResolveDummy.php
new file mode 100644
index 00000000000..d8f8199bf19
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/MultiRelationsResolveDummy.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\GraphQl\QueryCollection;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Dummy used in different kind of relations in the same resource.
+ *
+ * @author Thomas Helmrich
+ */
+#[ApiResource(graphQlOperations: [new Query(resolver: 'app.graphql.query_resolver.multi_relations_custom_item', read: false), new QueryCollection(resolver: 'app.graphql.query_resolver.multi_relations_collection', read: false)])]
+#[ODM\Document]
+class MultiRelationsResolveDummy
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private ?int $id = null;
+
+ #[ODM\Field(type: 'string', nullable: true)]
+ public ?string $name;
+
+ #[ODM\ReferenceOne(targetDocument: MultiRelationsDummy::class, inversedBy: 'oneToManyResolveRelations', nullable: true, storeAs: 'id')]
+ private ?MultiRelationsDummy $oneToManyResolveRelation;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getOneToManyResolveRelation(): ?MultiRelationsDummy
+ {
+ return $this->oneToManyResolveRelation;
+ }
+
+ public function setOneToManyResolveRelation(?MultiRelationsDummy $oneToManyResolveRelation): void
+ {
+ $this->oneToManyResolveRelation = $oneToManyResolveRelation;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php b/tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php
index cfc23adc998..f07edcd9313 100644
--- a/tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php
@@ -40,6 +40,9 @@ class MultiRelationsDummy
#[ORM\ManyToOne(targetEntity: MultiRelationsRelatedDummy::class)]
public ?MultiRelationsRelatedDummy $manyToOneRelation = null;
+ #[ORM\ManyToOne(targetEntity: MultiRelationsResolveDummy::class)]
+ public ?MultiRelationsResolveDummy $manyToOneResolveRelation = null;
+
/** @var Collection */
#[ORM\ManyToMany(targetEntity: MultiRelationsRelatedDummy::class)]
public Collection $manyToManyRelations;
@@ -79,6 +82,18 @@ public function setManyToOneRelation(?MultiRelationsRelatedDummy $relatedMultiUs
$this->manyToOneRelation = $relatedMultiUsedDummy;
}
+ public function getManyToOneResolveRelation(): ?MultiRelationsResolveDummy
+ {
+ return $this->manyToOneResolveRelation;
+ }
+
+ public function setManyToOneResolveRelation(?MultiRelationsResolveDummy $manyToOneResolveRelation): self
+ {
+ $this->manyToOneResolveRelation = $manyToOneResolveRelation;
+
+ return $this;
+ }
+
public function addManyToManyRelation(MultiRelationsRelatedDummy $relatedMultiUsedDummy): void
{
$this->manyToManyRelations->add($relatedMultiUsedDummy);
diff --git a/tests/Fixtures/TestBundle/Entity/MultiRelationsResolveDummy.php b/tests/Fixtures/TestBundle/Entity/MultiRelationsResolveDummy.php
new file mode 100644
index 00000000000..f724803e219
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/MultiRelationsResolveDummy.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\GraphQl\QueryCollection;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Dummy used in different kind of relations in the same resource.
+ *
+ * @author Thomas Helmrich
+ */
+#[ApiResource(graphQlOperations: [new Query(resolver: 'app.graphql.query_resolver.multi_relations_custom_item', read: false), new QueryCollection(resolver: 'app.graphql.query_resolver.multi_relations_collection', read: false)])]
+#[ORM\Entity]
+class MultiRelationsResolveDummy
+{
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+
+ #[ORM\Column(nullable: true)]
+ public ?string $name;
+
+ #[ORM\ManyToOne(targetEntity: MultiRelationsDummy::class, inversedBy: 'oneToManyResolveRelations')]
+ private ?MultiRelationsDummy $oneToManyResolveRelation = null;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getOneToManyResolveRelation(): ?MultiRelationsDummy
+ {
+ return $this->oneToManyResolveRelation;
+ }
+
+ public function setOneToManyResolveRelation(?MultiRelationsDummy $oneToManyResolveRelation): void
+ {
+ $this->oneToManyResolveRelation = $oneToManyResolveRelation;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemDocumentResolver.php b/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemDocumentResolver.php
index cbf5d8e9be4..16c7b12bf5e 100644
--- a/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemDocumentResolver.php
+++ b/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemDocumentResolver.php
@@ -15,7 +15,6 @@
use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\DummyCustomQuery as DummyCustomQueryDocument;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCustomQuery;
/**
* Resolver for dummy item custom query.
@@ -24,7 +23,7 @@
*/
class DummyCustomQueryNotRetrievedItemDocumentResolver implements QueryItemResolverInterface
{
- public function __invoke(?object $item, array $context): DummyCustomQuery|DummyCustomQueryDocument
+ public function __invoke(?object $item, array $context): DummyCustomQueryDocument
{
if (null === $item) {
$item = new DummyCustomQueryDocument();
diff --git a/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemResolver.php b/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemResolver.php
index 3d9ce628dee..fad1088d953 100644
--- a/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemResolver.php
+++ b/tests/Fixtures/TestBundle/GraphQl/Resolver/DummyCustomQueryNotRetrievedItemResolver.php
@@ -14,7 +14,6 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\GraphQl\Resolver;
use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\DummyCustomQuery as DummyCustomQueryDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCustomQuery;
/**
@@ -24,7 +23,7 @@
*/
class DummyCustomQueryNotRetrievedItemResolver implements QueryItemResolverInterface
{
- public function __invoke(?object $item, array $context): DummyCustomQuery|DummyCustomQueryDocument
+ public function __invoke(?object $item, array $context): DummyCustomQuery
{
if (null === $item) {
$item = new DummyCustomQuery();
diff --git a/tests/Fixtures/TestBundle/GraphQl/Resolver/MultiRelationsResolveQueryItemResolver.php b/tests/Fixtures/TestBundle/GraphQl/Resolver/MultiRelationsResolveQueryItemResolver.php
new file mode 100644
index 00000000000..9559d33a3c8
--- /dev/null
+++ b/tests/Fixtures/TestBundle/GraphQl/Resolver/MultiRelationsResolveQueryItemResolver.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\GraphQl\Resolver;
+
+use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
+use ApiPlatform\Tests\Fixtures\TestBundle\Document\MultiRelationsResolveDummy as MultiRelationsResolveDummyDocument;
+use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsResolveDummy;
+
+/**
+ * Resolver for dummy item custom query.
+ *
+ * @author Lukas Lücke
+ */
+class MultiRelationsResolveQueryItemResolver implements QueryItemResolverInterface
+{
+ public function __invoke(?object $item, array $context): MultiRelationsResolveDummy|MultiRelationsResolveDummyDocument
+ {
+ return $context['source']['manyToOneResolveRelation'];
+ }
+}
diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml
index 7bd2b6b82a5..d9edd6f3308 100644
--- a/tests/Fixtures/app/config/config_common.yml
+++ b/tests/Fixtures/app/config/config_common.yml
@@ -301,6 +301,11 @@ services:
tags:
- name: 'api_platform.graphql.resolver'
+ app.graphql.query_resolver.multi_relations_custom_item:
+ class: 'ApiPlatform\Tests\Fixtures\TestBundle\GraphQl\Resolver\MultiRelationsResolveQueryItemResolver'
+ tags:
+ - { name: 'api_platform.graphql.resolver' }
+
app.graphql.mutation_resolver.upload_media_object:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\GraphQl\Resolver\UploadMediaObjectResolver'
tags:
diff --git a/tests/Fixtures/app/config/config_mongodb.yml b/tests/Fixtures/app/config/config_mongodb.yml
index 8f42de9e3ab..975a11aab8d 100644
--- a/tests/Fixtures/app/config/config_mongodb.yml
+++ b/tests/Fixtures/app/config/config_mongodb.yml
@@ -110,6 +110,11 @@ services:
tags:
- name: 'api_platform.graphql.resolver'
+ app.graphql.query_resolver.multi_relations_custom_item_document:
+ class: 'ApiPlatform\Tests\Fixtures\TestBundle\GraphQl\Resolver\MultiRelationsResolveQueryItemResolver'
+ tags:
+ - { name: 'api_platform.graphql.resolver' }
+
app.graphql.mutation_resolver.dummy_custom_only_persist_document:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\GraphQl\Resolver\SumOnlyPersistDocumentMutationResolver'
public: false
diff --git a/tests/Symfony/Routing/ApiLoaderTest.php b/tests/Symfony/Routing/ApiLoaderTest.php
index 6766527825f..0c9ba60f018 100644
--- a/tests/Symfony/Routing/ApiLoaderTest.php
+++ b/tests/Symfony/Routing/ApiLoaderTest.php
@@ -213,6 +213,7 @@ public function testApiLoaderWithPrefix(): void
$prefixedPath = $prefix.$path;
+ $this->assertNotNull($routeCollection->get('api_errors'));
$this->assertEquals(
$this->getRoute(
$prefixedPath,