From f4c7d19976d8842a51bb8104044b82d147c6e817 Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Mon, 7 Nov 2022 15:21:30 +0100 Subject: [PATCH 1/9] fix(graphql): applied PR #4931 from webda2l --- src/Doctrine/Common/State/LinksHandlerTrait.php | 17 ++++++++++------- src/GraphQl/Resolver/Stage/ReadStage.php | 1 + .../LinkResourceMetadataCollectionFactory.php | 13 ++++++++----- tests/GraphQl/Resolver/Stage/ReadStageTest.php | 3 ++- ...inkResourceMetadataCollectionFactoryTest.php | 1 + 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Doctrine/Common/State/LinksHandlerTrait.php b/src/Doctrine/Common/State/LinksHandlerTrait.php index badd9d3df50..65aaf3ab447 100644 --- a/src/Doctrine/Common/State/LinksHandlerTrait.php +++ b/src/Doctrine/Common/State/LinksHandlerTrait.php @@ -34,11 +34,13 @@ private function getLinks(string $resourceClass, Operation $operation, array $co return $links; } - $newLinks = []; + $newLink = null; + $linkProperty = $context['linkProperty']; foreach ($links as $link) { - if ($linkClass === $link->getFromClass()) { - $newLinks[] = $link; + if (($linkClass === $link->getFromClass()) && ($linkProperty === $link->getFromProperty())) { + $newLink = $link; + break; } } @@ -62,16 +64,17 @@ private function getLinks(string $resourceClass, Operation $operation, array $co } foreach ($this->getOperationLinks($linkedOperation ?? null) as $link) { - if ($resourceClass === $link->getToClass()) { - $newLinks[] = $link; + if (($resourceClass === $link->getToClass()) && ($linkProperty === $link->getFromProperty())) { + $newLink = $link; + break; } } - if (!$newLinks) { + if (null === $newLink) { throw new RuntimeException(sprintf('The class "%s" cannot be retrieved from "%s".', $resourceClass, $linkClass)); } - return $newLinks; + return [$newLink]; } private function getIdentifierValue(array &$identifiers, string $name = null): mixed diff --git a/src/GraphQl/Resolver/Stage/ReadStage.php b/src/GraphQl/Resolver/Stage/ReadStage.php index b5c7d119030..527b7aea493 100644 --- a/src/GraphQl/Resolver/Stage/ReadStage.php +++ b/src/GraphQl/Resolver/Stage/ReadStage.php @@ -83,6 +83,7 @@ public function __invoke(?string $resourceClass, ?string $rootClass, Operation $ if (isset($source[$info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) { $uriVariables = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY]; $normalizationContext['linkClass'] = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY]; + $normalizationContext['linkProperty'] = $info->fieldName; } return $this->provider->provide($operation, $uriVariables, $normalizationContext); diff --git a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php index e8e7b9990f0..70544b7de68 100644 --- a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php @@ -68,18 +68,21 @@ private function mergeLinks(array $links, array $toMergeLinks): array { $classLinks = []; foreach ($links as $link) { - $classLinks[$link->getToClass()] = $link; + $classLinks[$link->getToClass()][$link->getFromProperty()] = $link; } foreach ($toMergeLinks as $link) { - if (isset($classLinks[$link->getToClass()])) { - $classLinks[$link->getToClass()] = $classLinks[$link->getToClass()]->withLink($link); + if (null !== $prevLink = $classLinks[$link->getToClass()][$link->getFromProperty()] ?? null) { + $classLinks[$link->getToClass()][$link->getFromProperty()] = $prevLink->withLink($link); continue; } - $classLinks[$link->getToClass()] = $link; + $classLinks[$link->getToClass()][$link->getFromProperty()] = $link; } - return array_values($classLinks); + // return array_values(array_merge(...array_values($classLinks))); (branch 3.0) + return array_reduce($classLinks, static function (array $carry, array $item) { + return array_merge($carry, array_values($item)); + }, []); } } diff --git a/tests/GraphQl/Resolver/Stage/ReadStageTest.php b/tests/GraphQl/Resolver/Stage/ReadStageTest.php index 95c53841691..40d81388f37 100644 --- a/tests/GraphQl/Resolver/Stage/ReadStageTest.php +++ b/tests/GraphQl/Resolver/Stage/ReadStageTest.php @@ -199,7 +199,8 @@ public function testApplyCollection(array $args, ?string $rootClass, ?array $sou $this->serializerContextBuilderProphecy->create($resourceClass, $operation, $context, true)->shouldBeCalled()->willReturn($normalizationContext); $this->providerProphecy->provide($operation, [], $normalizationContext + ['filters' => $expectedFilters])->willReturn([]); - $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource'])->willReturn(['resource']); + // $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource'])->willReturn(['resource']); + $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource', 'linkProperty' => 'subresource'])->willReturn(['subresource']); $result = ($this->readStage)($resourceClass, $rootClass, $operation, $context); diff --git a/tests/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactoryTest.php b/tests/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactoryTest.php index 174dcb1442b..790bc52fe4d 100644 --- a/tests/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactoryTest.php +++ b/tests/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactoryTest.php @@ -84,6 +84,7 @@ class: AttributeResource::class, class: AttributeResource::class, graphQlOperations: [ 'item_query' => (new Query(shortName: 'AttributeResource', class: AttributeResource::class))->withLinks([ + (new Link())->withFromProperty('foo')->withFromClass(AttributeResource::class)->withToClass(Dummy::class)->withIdentifiers(['id']), (new Link())->withFromProperty('foo2')->withFromClass(AttributeResource::class)->withToClass(Dummy::class)->withIdentifiers(['id']), (new Link())->withFromProperty('bar')->withFromClass(AttributeResource::class)->withToClass(RelatedDummy::class)->withIdentifiers(['id']), ]), From 665de9dc473057b4eca48f9a73dd5e102b65dfa4 Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Mon, 7 Nov 2022 15:28:45 +0100 Subject: [PATCH 2/9] chore: remove comment --- tests/GraphQl/Resolver/Stage/ReadStageTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/GraphQl/Resolver/Stage/ReadStageTest.php b/tests/GraphQl/Resolver/Stage/ReadStageTest.php index 40d81388f37..5540744e97f 100644 --- a/tests/GraphQl/Resolver/Stage/ReadStageTest.php +++ b/tests/GraphQl/Resolver/Stage/ReadStageTest.php @@ -199,7 +199,6 @@ public function testApplyCollection(array $args, ?string $rootClass, ?array $sou $this->serializerContextBuilderProphecy->create($resourceClass, $operation, $context, true)->shouldBeCalled()->willReturn($normalizationContext); $this->providerProphecy->provide($operation, [], $normalizationContext + ['filters' => $expectedFilters])->willReturn([]); - // $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource'])->willReturn(['resource']); $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource', 'linkProperty' => 'subresource'])->willReturn(['subresource']); $result = ($this->readStage)($resourceClass, $rootClass, $operation, $context); From 984f1fc3e0ae94aa6f68025ab94150af37deb80a Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Mon, 7 Nov 2022 15:35:35 +0100 Subject: [PATCH 3/9] test: updated for removed subresource reference --- tests/GraphQl/Resolver/Stage/ReadStageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQl/Resolver/Stage/ReadStageTest.php b/tests/GraphQl/Resolver/Stage/ReadStageTest.php index 5540744e97f..1022cf8347a 100644 --- a/tests/GraphQl/Resolver/Stage/ReadStageTest.php +++ b/tests/GraphQl/Resolver/Stage/ReadStageTest.php @@ -199,7 +199,7 @@ public function testApplyCollection(array $args, ?string $rootClass, ?array $sou $this->serializerContextBuilderProphecy->create($resourceClass, $operation, $context, true)->shouldBeCalled()->willReturn($normalizationContext); $this->providerProphecy->provide($operation, [], $normalizationContext + ['filters' => $expectedFilters])->willReturn([]); - $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource', 'linkProperty' => 'subresource'])->willReturn(['subresource']); + $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource', 'linkProperty' => 'resource'])->willReturn(['resource']); $result = ($this->readStage)($resourceClass, $rootClass, $operation, $context); From 810194757853e43c4384b43ff0777c12bae89fc2 Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Mon, 7 Nov 2022 18:52:05 +0100 Subject: [PATCH 4/9] test: added a oneToMany Relation to a Entity that already is referenced in another relation to test the success of nested GraphQL query --- tests/Fixtures/TestBundle/Entity/Dummy.php | 14 ++++++++++++++ tests/Fixtures/TestBundle/Entity/RelatedDummy.php | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tests/Fixtures/TestBundle/Entity/Dummy.php b/tests/Fixtures/TestBundle/Entity/Dummy.php index 69e9384db63..fdc5e158359 100644 --- a/tests/Fixtures/TestBundle/Entity/Dummy.php +++ b/tests/Fixtures/TestBundle/Entity/Dummy.php @@ -106,6 +106,9 @@ class Dummy #[ORM\ManyToMany(targetEntity: RelatedDummy::class)] public Collection|iterable $relatedDummies; + #[ORM\OneToMany(targetEntity: RelatedDummy::class, mappedBy: 'otherRelatedDummy')] + public Collection|iterable $otherRelatedDummies; + /** * @var array|null serialize data */ @@ -143,6 +146,7 @@ public static function staticMethod(): void public function __construct() { $this->relatedDummies = new ArrayCollection(); + $this->otherRelatedDummies = new ArrayCollection(); } public function getId() @@ -256,6 +260,11 @@ public function addRelatedDummy(RelatedDummy $relatedDummy): void $this->relatedDummies->add($relatedDummy); } + public function addOtherRelatedDummy(RelatedDummy $relatedDummy): void + { + $this->otherRelatedDummies->add($relatedDummy); + } + public function getRelatedOwnedDummy() { return $this->relatedOwnedDummy; @@ -306,4 +315,9 @@ public function getRelatedDummies(): Collection|iterable { return $this->relatedDummies; } + + public function getOtherRelatedDummies(): Collection|iterable + { + return $this->otherRelatedDummies; + } } diff --git a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php index a6e60f79169..86c25bd0b20 100644 --- a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php +++ b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php @@ -104,6 +104,8 @@ class RelatedDummy extends ParentDummy implements \Stringable #[ORM\Embedded(class: 'EmbeddableDummy')] #[Groups(['friends'])] public ?EmbeddableDummy $embeddedDummy = null; + #[ORM\ManyToOne(targetEntity: Dummy::class, inversedBy: 'otherRelatedDummies')] + protected ?Dummy $otherRelatedDummy = null; public function __construct() { @@ -202,6 +204,17 @@ public function setEmbeddedDummy(EmbeddableDummy $embeddedDummy): void $this->embeddedDummy = $embeddedDummy; } + public function getOtherRelatedDummy(): ?Dummy + { + return $this->otherRelatedDummy; + } + + public function setOtherRelatedDummy(Dummy $otherRelatedDummy): void + { + $this->otherRelatedDummy = $otherRelatedDummy; + } + + public function __toString(): string { return (string) $this->getId(); From 54cc00dce9dd4a4c233bfca0918adba49ef984e0 Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Mon, 7 Nov 2022 18:54:08 +0100 Subject: [PATCH 5/9] test: added test to check nested queries on an entity with a oneToMany relation targeting a already related entity. --- features/graphql/query.feature | 49 ++++++++++++++++++++ features/json/relation.feature | 1 + features/main/content_negotiation.feature | 9 ++-- features/main/crud.feature | 4 ++ features/main/relation.feature | 1 + features/main/sub_resource.feature | 2 + features/security/strong_typing.feature | 1 + features/security/unknown_attributes.feature | 1 + tests/Behat/DoctrineContext.php | 40 ++++++++++++++++ 9 files changed, 104 insertions(+), 4 deletions(-) diff --git a/features/graphql/query.feature b/features/graphql/query.feature index 45f546066fc..6d6614dfc03 100644 --- a/features/graphql/query.feature +++ b/features/graphql/query.feature @@ -449,3 +449,52 @@ Feature: GraphQL query support And the header "Content-Type" should be equal to "application/json" And the JSON node "data.dummyDifferentGraphQlSerializationGroup.name" should be equal to "Name #1" And the JSON node "data.dummyDifferentGraphQlSerializationGroup.title" should be equal to "Title #1" + + @createSchema + Scenario: Execute a GraphQL query that includes a nested collection + Given there are 1 dummy objects having each 2 relatedDummies with relatedDummy having each 3 otherRelatedDummies + When I send the following GraphQL request: + """ + { + dummy(id: "/dummies/1") { + id + name + relatedDummy { + id + name + } + relatedDummies { + edges{ + node { + id + name + } + } + } + otherRelatedDummies { + edges{ + node { + id + 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 "data.dummy.id" should be equal to "/dummies/1" + And the JSON node "data.dummy.name" should be equal to "Dummy #1" + And the JSON node "data.dummy.relatedDummy.id" should not be null + And the JSON node "data.dummy.relatedDummy.name" should be equal to "RelatedDummy #1" + And the JSON node "data.dummy.relatedDummies.edges" should have 2 element + And the JSON node "data.dummy.relatedDummies.edges[1].node.id" should not be null + And the JSON node "data.dummy.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy11" + And the JSON node "data.dummy.relatedDummies.edges[1].node.name" should be equal to "RelatedDummy21" + And the JSON node "data.dummy.otherRelatedDummies.edges" should have 3 element + And the JSON node "data.dummy.otherRelatedDummies.edges[1].node.id" should not be null + And the JSON node "data.dummy.otherRelatedDummies.edges[0].node.name" should be equal to "OtherRelatedDummy11" + And the JSON node "data.dummy.otherRelatedDummies.edges[2].node.name" should be equal to "OtherRelatedDummy31" + diff --git a/features/json/relation.feature b/features/json/relation.feature index 0991407a8d1..f2a453abaae 100644 --- a/features/json/relation.feature +++ b/features/json/relation.feature @@ -215,6 +215,7 @@ Feature: JSON relations support "relatedDummies": [ "/related_dummies/1" ], + "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/main/content_negotiation.feature b/features/main/content_negotiation.feature index 7f22db3b396..ed14adca629 100644 --- a/features/main/content_negotiation.feature +++ b/features/main/content_negotiation.feature @@ -19,7 +19,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 1XML! + 1XML! """ Scenario: Retrieve a collection in XML @@ -31,7 +31,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 1XML! + 1XML! """ Scenario: Retrieve a collection in XML using the .xml URL @@ -42,7 +42,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 1XML! + 1XML! """ Scenario: Retrieve a collection in JSON @@ -63,6 +63,7 @@ Feature: Content Negotiation support "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, @@ -89,7 +90,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 2Sent in JSON + 2Sent in JSON """ Scenario: Requesting the same format in the Accept header and in the URL should work diff --git a/features/main/crud.feature b/features/main/crud.feature index 1fc310d63bb..1b4e31a3fd9 100644 --- a/features/main/crud.feature +++ b/features/main/crud.feature @@ -38,6 +38,7 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": { "key": [ "value1", @@ -74,6 +75,7 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": { "key": [ "value1", @@ -124,6 +126,7 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": { "key": [ "value1", @@ -528,6 +531,7 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": [ { "key": "value1" diff --git a/features/main/relation.feature b/features/main/relation.feature index 760da8008fb..e89e97ad49e 100644 --- a/features/main/relation.feature +++ b/features/main/relation.feature @@ -162,6 +162,7 @@ Feature: Relations support "relatedDummies": [ "/related_dummies/1" ], + "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/main/sub_resource.feature b/features/main/sub_resource.feature index 77563e51cb3..1f0e75b458c 100644 --- a/features/main/sub_resource.feature +++ b/features/main/sub_resource.feature @@ -517,6 +517,7 @@ Feature: Sub-resource support "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, @@ -550,6 +551,7 @@ Feature: Sub-resource support "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/security/strong_typing.feature b/features/security/strong_typing.feature index 3d4b7599a62..993bb4ed8f1 100644 --- a/features/security/strong_typing.feature +++ b/features/security/strong_typing.feature @@ -30,6 +30,7 @@ Feature: Handle properly invalid data submitted to the API "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/security/unknown_attributes.feature b/features/security/unknown_attributes.feature index efe7b954c0a..fafe03fd434 100644 --- a/features/security/unknown_attributes.feature +++ b/features/security/unknown_attributes.feature @@ -30,6 +30,7 @@ Feature: Ignore unknown attributes "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], + "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php index 37aaa67205b..8ce8a6fa54b 100644 --- a/tests/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -759,6 +759,46 @@ public function thereAreDummyObjectsWithRelatedDummies(int $nb, int $nbrelated): $this->manager->flush(); } + /** + * @Given there are :nb dummy objects having each :nbrelated relatedDummies with relatedDummy having each :nbotherrelated otherRelatedDummies + */ + public function thereAreDummyObjectsWithRelatedDummiesWithRelatedDummy(int $nb, int $nbrelated, int $nbotherrelated): void + { + for ($i = 1; $i <= $nb; ++$i) { + $relatedDummy = $this->buildRelatedDummy(); + $relatedDummy->setName('RelatedDummy #'.$i); + + $dummy = $this->buildDummy(); + $dummy->setName('Dummy #'.$i); + $dummy->setAlias('Alias #'.($nb - $i)); + $dummy->setRelatedDummy($relatedDummy); + + for ($j = 1; $j <= $nbrelated; ++$j) { + $relatedCollectionDummy = $this->buildRelatedDummy(); + $relatedCollectionDummy->setName('RelatedDummy'.$j.$i); + $relatedCollectionDummy->setAge((int) ($j.$i)); + $this->manager->persist($relatedCollectionDummy); + + $dummy->addRelatedDummy($relatedCollectionDummy); + } + + for ($j = 1; $j <= $nbotherrelated; ++$j) { + $relatedCollectionDummy = $this->buildRelatedDummy(); + $relatedCollectionDummy->setName('OtherRelatedDummy'.$j.$i); + $relatedCollectionDummy->setAge((int) ($j.$i)); + $relatedCollectionDummy->setOtherRelatedDummy($dummy); + $this->manager->persist($relatedCollectionDummy); + + $dummy->addOtherRelatedDummy($relatedCollectionDummy); + } + + $this->manager->persist($relatedDummy); + $this->manager->persist($dummy); + + } + $this->manager->flush(); + } + /** * @Given there are :nb dummy objects with dummyDate * @Given there is :nb dummy object with dummyDate From 1ffe89c1c232b2ca79accf9177db3c45718c7340 Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Tue, 8 Nov 2022 12:22:26 +0100 Subject: [PATCH 6/9] test: revert usage of overused Dummy & RelatedDummy --- features/json/relation.feature | 1 - features/main/content_negotiation.feature | 9 ++++----- features/main/crud.feature | 4 ---- features/main/relation.feature | 1 - features/main/sub_resource.feature | 2 -- features/security/strong_typing.feature | 1 - features/security/unknown_attributes.feature | 1 - tests/Fixtures/TestBundle/Entity/Dummy.php | 14 -------------- tests/Fixtures/TestBundle/Entity/RelatedDummy.php | 13 ------------- 9 files changed, 4 insertions(+), 42 deletions(-) diff --git a/features/json/relation.feature b/features/json/relation.feature index f2a453abaae..0991407a8d1 100644 --- a/features/json/relation.feature +++ b/features/json/relation.feature @@ -215,7 +215,6 @@ Feature: JSON relations support "relatedDummies": [ "/related_dummies/1" ], - "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/main/content_negotiation.feature b/features/main/content_negotiation.feature index ed14adca629..7f22db3b396 100644 --- a/features/main/content_negotiation.feature +++ b/features/main/content_negotiation.feature @@ -19,7 +19,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 1XML! + 1XML! """ Scenario: Retrieve a collection in XML @@ -31,7 +31,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 1XML! + 1XML! """ Scenario: Retrieve a collection in XML using the .xml URL @@ -42,7 +42,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 1XML! + 1XML! """ Scenario: Retrieve a collection in JSON @@ -63,7 +63,6 @@ Feature: Content Negotiation support "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, @@ -90,7 +89,7 @@ Feature: Content Negotiation support And the XML should be equal to: """ - 2Sent in JSON + 2Sent in JSON """ Scenario: Requesting the same format in the Accept header and in the URL should work diff --git a/features/main/crud.feature b/features/main/crud.feature index 1b4e31a3fd9..1fc310d63bb 100644 --- a/features/main/crud.feature +++ b/features/main/crud.feature @@ -38,7 +38,6 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": { "key": [ "value1", @@ -75,7 +74,6 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": { "key": [ "value1", @@ -126,7 +124,6 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": { "key": [ "value1", @@ -531,7 +528,6 @@ Feature: Create-Retrieve-Update-Delete "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": [ { "key": "value1" diff --git a/features/main/relation.feature b/features/main/relation.feature index e89e97ad49e..760da8008fb 100644 --- a/features/main/relation.feature +++ b/features/main/relation.feature @@ -162,7 +162,6 @@ Feature: Relations support "relatedDummies": [ "/related_dummies/1" ], - "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/main/sub_resource.feature b/features/main/sub_resource.feature index 1f0e75b458c..77563e51cb3 100644 --- a/features/main/sub_resource.feature +++ b/features/main/sub_resource.feature @@ -517,7 +517,6 @@ Feature: Sub-resource support "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, @@ -551,7 +550,6 @@ Feature: Sub-resource support "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/security/strong_typing.feature b/features/security/strong_typing.feature index 993bb4ed8f1..3d4b7599a62 100644 --- a/features/security/strong_typing.feature +++ b/features/security/strong_typing.feature @@ -30,7 +30,6 @@ Feature: Handle properly invalid data submitted to the API "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/features/security/unknown_attributes.feature b/features/security/unknown_attributes.feature index fafe03fd434..efe7b954c0a 100644 --- a/features/security/unknown_attributes.feature +++ b/features/security/unknown_attributes.feature @@ -30,7 +30,6 @@ Feature: Ignore unknown attributes "dummyPrice": null, "relatedDummy": null, "relatedDummies": [], - "otherRelatedDummies": [], "jsonData": [], "arrayData": [], "name_converted": null, diff --git a/tests/Fixtures/TestBundle/Entity/Dummy.php b/tests/Fixtures/TestBundle/Entity/Dummy.php index fdc5e158359..69e9384db63 100644 --- a/tests/Fixtures/TestBundle/Entity/Dummy.php +++ b/tests/Fixtures/TestBundle/Entity/Dummy.php @@ -106,9 +106,6 @@ class Dummy #[ORM\ManyToMany(targetEntity: RelatedDummy::class)] public Collection|iterable $relatedDummies; - #[ORM\OneToMany(targetEntity: RelatedDummy::class, mappedBy: 'otherRelatedDummy')] - public Collection|iterable $otherRelatedDummies; - /** * @var array|null serialize data */ @@ -146,7 +143,6 @@ public static function staticMethod(): void public function __construct() { $this->relatedDummies = new ArrayCollection(); - $this->otherRelatedDummies = new ArrayCollection(); } public function getId() @@ -260,11 +256,6 @@ public function addRelatedDummy(RelatedDummy $relatedDummy): void $this->relatedDummies->add($relatedDummy); } - public function addOtherRelatedDummy(RelatedDummy $relatedDummy): void - { - $this->otherRelatedDummies->add($relatedDummy); - } - public function getRelatedOwnedDummy() { return $this->relatedOwnedDummy; @@ -315,9 +306,4 @@ public function getRelatedDummies(): Collection|iterable { return $this->relatedDummies; } - - public function getOtherRelatedDummies(): Collection|iterable - { - return $this->otherRelatedDummies; - } } diff --git a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php index 86c25bd0b20..a6e60f79169 100644 --- a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php +++ b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php @@ -104,8 +104,6 @@ class RelatedDummy extends ParentDummy implements \Stringable #[ORM\Embedded(class: 'EmbeddableDummy')] #[Groups(['friends'])] public ?EmbeddableDummy $embeddedDummy = null; - #[ORM\ManyToOne(targetEntity: Dummy::class, inversedBy: 'otherRelatedDummies')] - protected ?Dummy $otherRelatedDummy = null; public function __construct() { @@ -204,17 +202,6 @@ public function setEmbeddedDummy(EmbeddableDummy $embeddedDummy): void $this->embeddedDummy = $embeddedDummy; } - public function getOtherRelatedDummy(): ?Dummy - { - return $this->otherRelatedDummy; - } - - public function setOtherRelatedDummy(Dummy $otherRelatedDummy): void - { - $this->otherRelatedDummy = $otherRelatedDummy; - } - - public function __toString(): string { return (string) $this->getId(); From 823a324e7e1c033f211f5f35782c0c0e97d68d55 Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Tue, 8 Nov 2022 14:08:42 +0100 Subject: [PATCH 7/9] test: added separate entity / document to test nested GraphQL query fix --- features/graphql/query.feature | 39 ++++--- tests/Behat/DoctrineContext.php | 51 +++++---- .../Document/RelatedMultiUsedDummy.php | 77 +++++++++++++ .../Document/SameRelationMultiUseDummy.php | 102 +++++++++++++++++ .../Entity/RelatedMultiUsedDummy.php | 79 +++++++++++++ .../Entity/SameRelationMultiUseDummy.php | 106 ++++++++++++++++++ .../State/DummyDtoInputOutputProvider.php | 1 + 7 files changed, 415 insertions(+), 40 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php create mode 100644 tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php create mode 100644 tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php create mode 100644 tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php diff --git a/features/graphql/query.feature b/features/graphql/query.feature index 6d6614dfc03..58acbfb0796 100644 --- a/features/graphql/query.feature +++ b/features/graphql/query.feature @@ -451,27 +451,27 @@ Feature: GraphQL query support And the JSON node "data.dummyDifferentGraphQlSerializationGroup.title" should be equal to "Title #1" @createSchema - Scenario: Execute a GraphQL query that includes a nested collection - Given there are 1 dummy objects having each 2 relatedDummies with relatedDummy having each 3 otherRelatedDummies + Scenario: Execute a GraphQL query that includes a nested collection with different relations to same entity + Given there are 3 sameRelationMultiUseDummy objects having a manyToOneRelation and each 2 manyToManyRelations and having each 3 oneToManyRelations When I send the following GraphQL request: """ { - dummy(id: "/dummies/1") { + sameRelationMultiUseDummy(id: "/same_relation_multi_use_dummies/2") { id name - relatedDummy { + manyToOneRelation { id name } - relatedDummies { + manyToManyRelations { edges{ node { - id + id name } } } - otherRelatedDummies { + oneToManyRelations { edges{ node { id @@ -485,16 +485,15 @@ 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 "data.dummy.id" should be equal to "/dummies/1" - And the JSON node "data.dummy.name" should be equal to "Dummy #1" - And the JSON node "data.dummy.relatedDummy.id" should not be null - And the JSON node "data.dummy.relatedDummy.name" should be equal to "RelatedDummy #1" - And the JSON node "data.dummy.relatedDummies.edges" should have 2 element - And the JSON node "data.dummy.relatedDummies.edges[1].node.id" should not be null - And the JSON node "data.dummy.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy11" - And the JSON node "data.dummy.relatedDummies.edges[1].node.name" should be equal to "RelatedDummy21" - And the JSON node "data.dummy.otherRelatedDummies.edges" should have 3 element - And the JSON node "data.dummy.otherRelatedDummies.edges[1].node.id" should not be null - And the JSON node "data.dummy.otherRelatedDummies.edges[0].node.name" should be equal to "OtherRelatedDummy11" - And the JSON node "data.dummy.otherRelatedDummies.edges[2].node.name" should be equal to "OtherRelatedDummy31" - + And the JSON node "data.sameRelationMultiUseDummy.id" should be equal to "/same_relation_multi_use_dummies/2" + And the JSON node "data.sameRelationMultiUseDummy.name" should be equal to "Dummy #2" + And the JSON node "data.sameRelationMultiUseDummy.manyToOneRelation.id" should not be null + And the JSON node "data.sameRelationMultiUseDummy.manyToOneRelation.name" should be equal to "RelatedMultiUsedDummy #2" + And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges" should have 2 element + And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges[1].node.id" should not be null + And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges[0].node.name" should be equal to "RelatedManyToManyDummy12" + And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges[1].node.name" should be equal to "RelatedManyToManyDummy22" + And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges" should have 3 element + And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges[1].node.id" should not be null + And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges[0].node.name" should be equal to "RelatedOneToManyDummy12" + And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges[2].node.name" should be equal to "RelatedOneToManyDummy32" diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php index 8ce8a6fa54b..023c3356ea8 100644 --- a/tests/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -78,8 +78,10 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedOwnedDummy as RelatedOwnedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedOwningDummy as RelatedOwningDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedSecuredDummy as RelatedSecuredDummyDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedMultiUsedDummy as RelatedMultiUsedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedToDummyFriend as RelatedToDummyFriendDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelationEmbedder as RelationEmbedderDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\SameRelationMultiUseDummy as SameRelationMultiUseDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\SecuredDummy as SecuredDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\SoMany as SoManyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Taxon as TaxonDocument; @@ -152,11 +154,13 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RamseyUuidDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedMultiUsedDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwnedDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwningDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedSecuredDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedToDummyFriend; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelationEmbedder; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SameRelationMultiUseDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Site; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SoMany; @@ -760,36 +764,33 @@ public function thereAreDummyObjectsWithRelatedDummies(int $nb, int $nbrelated): } /** - * @Given there are :nb dummy objects having each :nbrelated relatedDummies with relatedDummy having each :nbotherrelated otherRelatedDummies + * @Given there are :nb sameRelationMultiUseDummy objects having a manyToOneRelation and each :nbmtmr manyToManyRelations and having each :nbotmr oneToManyRelations */ - public function thereAreDummyObjectsWithRelatedDummiesWithRelatedDummy(int $nb, int $nbrelated, int $nbotherrelated): void + public function thereAreSameRelationMultiUseDummyObjectsHavingAManyToOneRelationAndEachManyToManyRelationsAndHavingEachOneToManyRelations(int $nb, int $nbmtmr, int $nbotmr): void { for ($i = 1; $i <= $nb; ++$i) { - $relatedDummy = $this->buildRelatedDummy(); - $relatedDummy->setName('RelatedDummy #'.$i); + $relatedDummy = $this->buildRelatedMultiUsedDummy(); + $relatedDummy->setName('RelatedMultiUsedDummy #'.$i); - $dummy = $this->buildDummy(); + $dummy = $this->buildSameRelationMultiUseDummy(); $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setRelatedDummy($relatedDummy); + $dummy->setManyToOneRelation($relatedDummy); - for ($j = 1; $j <= $nbrelated; ++$j) { - $relatedCollectionDummy = $this->buildRelatedDummy(); - $relatedCollectionDummy->setName('RelatedDummy'.$j.$i); - $relatedCollectionDummy->setAge((int) ($j.$i)); - $this->manager->persist($relatedCollectionDummy); + for ($j = 1; $j <= $nbmtmr; ++$j) { + $manyToManyItem = $this->buildRelatedMultiUsedDummy(); + $manyToManyItem->setName('RelatedManyToManyDummy'.$j.$i); + $this->manager->persist($manyToManyItem); - $dummy->addRelatedDummy($relatedCollectionDummy); + $dummy->addManyToManyRelation($manyToManyItem); } - for ($j = 1; $j <= $nbotherrelated; ++$j) { - $relatedCollectionDummy = $this->buildRelatedDummy(); - $relatedCollectionDummy->setName('OtherRelatedDummy'.$j.$i); - $relatedCollectionDummy->setAge((int) ($j.$i)); - $relatedCollectionDummy->setOtherRelatedDummy($dummy); - $this->manager->persist($relatedCollectionDummy); + for ($j = 1; $j <= $nbotmr; ++$j) { + $oneToManyItem = $this->buildRelatedMultiUsedDummy(); + $oneToManyItem->setName('RelatedOneToManyDummy'.$j.$i); + $oneToManyItem->setOneToManyRelation($dummy); + $this->manager->persist($oneToManyItem); - $dummy->addOtherRelatedDummy($relatedCollectionDummy); + $dummy->addOneToManyRelation($oneToManyItem); } $this->manager->persist($relatedDummy); @@ -2340,4 +2341,14 @@ private function buildPayment(string $amount): Payment|PaymentDocument { return $this->isOrm() ? new Payment($amount) : new PaymentDocument($amount); } + + private function buildSameRelationMultiUseDummy(): SameRelationMultiUseDummy|SameRelationMultiUseDummyDocument + { + return $this->isOrm() ? new SameRelationMultiUseDummy() : new SameRelationMultiUseDummyDocument(); + } + + private function buildRelatedMultiUsedDummy(): RelatedMultiUsedDummy|RelatedMultiUsedDummyDocument + { + return $this->isOrm() ? new RelatedMultiUsedDummy() : new RelatedMultiUsedDummyDocument(); + } } diff --git a/tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php b/tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php new file mode 100644 index 00000000000..c63b7801dfc --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php @@ -0,0 +1,77 @@ + + * + * 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\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; + +/** + * Related Multi Used Dummy. + * + * @author Thomas Helmrich + */ +#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] +#[ODM\Document] +class RelatedMultiUsedDummy +{ + #[ApiProperty(writable: false)] + #[ODM\Id(strategy: 'INCREMENT', type: 'int')] + private $id; + + /** + * @var string|null A name + */ + #[ODM\Field(type: 'string', nullable: true)] + public $name; + + #[ODM\ReferenceOne(targetDocument: SameRelationMultiUseDummy::class, inversedBy: 'oneToManyRelations', nullable: true, storeAs: 'id')] + protected ?SameRelationMultiUseDummy $oneToManyRelation = null; + + public function __construct() + { + } + + public function getId() + { + return $this->id; + } + + public function setId($id): void + { + $this->id = $id; + } + + public function setName($name): void + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function getOneToManyRelation(): ?SameRelationMultiUseDummy + { + return $this->oneToManyRelation; + } + + public function setOneToManyRelation(SameRelationMultiUseDummy $oneToManyRelation): void + { + $this->oneToManyRelation = $oneToManyRelation; + } +} diff --git a/tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php b/tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php new file mode 100644 index 00000000000..87c66abb908 --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php @@ -0,0 +1,102 @@ + + * + * 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\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * Same relation Multi Use Dummy. + * + * @author Thomas Helmrich + */ +#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] +#[ODM\Document] +class SameRelationMultiUseDummy +{ + /** + * @var int|null The id + */ + #[ODM\Id(strategy: 'INCREMENT', type: 'int')] + private $id; + + /** + * @var string The same relation multi use dummy name + */ + #[ApiProperty(iris: ['https://schema.org/name'])] + #[ODM\Field(type: 'string')] + #[Assert\NotBlank] + private string $name; + + #[ODM\ReferenceOne(targetDocument: RelatedMultiUsedDummy::class, storeAs: 'id', nullable: true)] + public ?RelatedMultiUsedDummy $manyToOneRelation = null; + + #[ODM\ReferenceMany(targetDocument: RelatedMultiUsedDummy::class, storeAs: 'id', nullable: true)] + public Collection|iterable $manyToManyRelations; + + #[ODM\ReferenceMany(targetDocument: RelatedMultiUsedDummy::class, mappedBy: 'oneToManyRelation', storeAs: 'id')] + public Collection|iterable $oneToManyRelations; + + public function __construct() + { + $this->manyToManyRelations = new ArrayCollection(); + $this->oneToManyRelations = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + public function setId($id): void + { + $this->id = $id; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function getManyToOneRelation(): ?RelatedMultiUsedDummy + { + return $this->manyToOneRelation; + } + + public function setManyToOneRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void + { + $this->manyToOneRelation = $relatedMultiUsedDummy; + } + + public function addManyToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void + { + $this->manyToManyRelations->add($relatedMultiUsedDummy); + } + + public function addOneToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void + { + $this->oneToManyRelations->add($relatedMultiUsedDummy); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php new file mode 100644 index 00000000000..f64b7177878 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php @@ -0,0 +1,79 @@ + + * + * 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\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; +use Doctrine\ORM\Mapping as ORM; + +/** + * Related Multi Used Dummy. + * + * @author Thomas Helmrich + */ +#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] +#[ORM\Entity] +class RelatedMultiUsedDummy +{ + #[ApiProperty(writable: false)] + #[ORM\Column(type: 'integer')] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] + private $id; + + /** + * @var string|null A name + */ + #[ORM\Column(nullable: true)] + public $name; + + #[ORM\ManyToOne(targetEntity: SameRelationMultiUseDummy::class, inversedBy: 'oneToManyRelations')] + protected ?SameRelationMultiUseDummy $oneToManyRelation = null; + + public function __construct() + { + } + + public function getId() + { + return $this->id; + } + + public function setId($id): void + { + $this->id = $id; + } + + public function setName($name): void + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function getOneToManyRelation(): ?SameRelationMultiUseDummy + { + return $this->oneToManyRelation; + } + + public function setOneToManyRelation(SameRelationMultiUseDummy $oneToManyRelation): void + { + $this->oneToManyRelation = $oneToManyRelation; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php b/tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php new file mode 100644 index 00000000000..b11ffa016ff --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php @@ -0,0 +1,106 @@ + + * + * 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\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; +use ApiPlatform\Metadata\Link; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * Same relation Multi Use Dummy. + * + * @author Thomas Helmrich + */ +#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] +#[ORM\Entity] +class SameRelationMultiUseDummy +{ + /** + * @var int|null The id + */ + #[ORM\Column(type: 'integer', nullable: true)] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] + private $id; + + /** + * @var string The same relation multi use dummy name + */ + #[ApiProperty(iris: ['https://schema.org/name'])] + #[ORM\Column] + #[Assert\NotBlank] + private string $name; + + #[ORM\ManyToOne(targetEntity: RelatedMultiUsedDummy::class)] + public ?RelatedMultiUsedDummy $manyToOneRelation = null; + + #[ORM\ManyToMany(targetEntity: RelatedMultiUsedDummy::class)] + public Collection|iterable $manyToManyRelations; + + #[ORM\OneToMany(targetEntity: RelatedMultiUsedDummy::class, mappedBy: 'oneToManyRelation')] + public Collection|iterable $oneToManyRelations; + + public function __construct() + { + $this->manyToManyRelations = new ArrayCollection(); + $this->oneToManyRelations = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + public function setId($id): void + { + $this->id = $id; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function getManyToOneRelation(): ?RelatedMultiUsedDummy + { + return $this->manyToOneRelation; + } + + public function setManyToOneRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void + { + $this->manyToOneRelation = $relatedMultiUsedDummy; + } + + public function addManyToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void + { + $this->manyToManyRelations->add($relatedMultiUsedDummy); + } + + public function addOneToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void + { + $this->oneToManyRelations->add($relatedMultiUsedDummy); + } +} diff --git a/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php b/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php index 0355d49cafb..efd62bccb5f 100644 --- a/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php +++ b/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php @@ -19,6 +19,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Dto\Document\OutputDto as OutputDtoDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyDtoInputOutput; +use Doctrine\ORM\PersistentCollection; final class DummyDtoInputOutputProvider implements ProviderInterface { From 02194e5fb90d409106705c77c89906759304367c Mon Sep 17 00:00:00 2001 From: Thomas Helmrich Date: Tue, 8 Nov 2022 14:09:15 +0100 Subject: [PATCH 8/9] chore: updated to functionality for 3.0 branch --- .../Factory/LinkResourceMetadataCollectionFactory.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php index 70544b7de68..37689e7be50 100644 --- a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php @@ -80,9 +80,6 @@ private function mergeLinks(array $links, array $toMergeLinks): array $classLinks[$link->getToClass()][$link->getFromProperty()] = $link; } - // return array_values(array_merge(...array_values($classLinks))); (branch 3.0) - return array_reduce($classLinks, static function (array $carry, array $item) { - return array_merge($carry, array_values($item)); - }, []); + return array_values(array_merge(...array_values($classLinks))); } } From f2f1da216a0ed7823233e6dd9daeab1c3829e0bb Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Tue, 13 Dec 2022 11:59:47 +0100 Subject: [PATCH 9/9] chore: fixes --- features/graphql/query.feature | 96 ++++++++-------- .../Common/State/LinksHandlerTrait.php | 12 +- .../LinkResourceMetadataCollectionFactory.php | 10 +- tests/Behat/DoctrineContext.php | 37 +++--- .../Document/MultiRelationsDummy.php | 79 +++++++++++++ .../Document/MultiRelationsRelatedDummy.php | 53 +++++++++ .../Document/RelatedMultiUsedDummy.php | 77 ------------- .../Document/SameRelationMultiUseDummy.php | 102 ----------------- .../TestBundle/Entity/MultiRelationsDummy.php | 81 +++++++++++++ .../Entity/MultiRelationsRelatedDummy.php | 55 +++++++++ .../Entity/RelatedMultiUsedDummy.php | 79 ------------- .../Entity/SameRelationMultiUseDummy.php | 106 ------------------ .../State/DummyDtoInputOutputProvider.php | 1 - 13 files changed, 347 insertions(+), 441 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php create mode 100644 tests/Fixtures/TestBundle/Document/MultiRelationsRelatedDummy.php delete mode 100644 tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php delete mode 100644 tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php create mode 100644 tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php create mode 100644 tests/Fixtures/TestBundle/Entity/MultiRelationsRelatedDummy.php delete mode 100644 tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php delete mode 100644 tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php diff --git a/features/graphql/query.feature b/features/graphql/query.feature index 58acbfb0796..4463a717b87 100644 --- a/features/graphql/query.feature +++ b/features/graphql/query.feature @@ -20,6 +20,54 @@ Feature: GraphQL query support And the JSON node "data.dummy.name" should be equal to "Dummy #1" And the JSON node "data.dummy.name_converted" should be equal to "Converted 1" + @createSchema + Scenario: Retrieve an item with different relations to the same resource + Given there are 2 multiRelationsDummy objects having each a manyToOneRelation, 2 manyToManyRelations and 3 oneToManyRelations + When I send the following GraphQL request: + """ + { + multiRelationsDummy(id: "/multi_relations_dummies/2") { + id + name + manyToOneRelation { + id + name + } + manyToManyRelations { + edges{ + node { + id + name + } + } + } + oneToManyRelations { + edges{ + node { + id + 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 "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.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 be equal to "RelatedManyToManyDummy12" + And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges[1].node.name" should be equal to "RelatedManyToManyDummy22" + And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges" should have 3 element + And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges[1].node.id" should not be null + And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges[0].node.name" should be equal to "RelatedOneToManyDummy12" + And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges[2].node.name" should be equal to "RelatedOneToManyDummy32" + @createSchema Scenario: Retrieve a Relay Node Given there are 2 dummy objects with relatedDummy @@ -449,51 +497,3 @@ Feature: GraphQL query support And the header "Content-Type" should be equal to "application/json" And the JSON node "data.dummyDifferentGraphQlSerializationGroup.name" should be equal to "Name #1" And the JSON node "data.dummyDifferentGraphQlSerializationGroup.title" should be equal to "Title #1" - - @createSchema - Scenario: Execute a GraphQL query that includes a nested collection with different relations to same entity - Given there are 3 sameRelationMultiUseDummy objects having a manyToOneRelation and each 2 manyToManyRelations and having each 3 oneToManyRelations - When I send the following GraphQL request: - """ - { - sameRelationMultiUseDummy(id: "/same_relation_multi_use_dummies/2") { - id - name - manyToOneRelation { - id - name - } - manyToManyRelations { - edges{ - node { - id - name - } - } - } - oneToManyRelations { - edges{ - node { - id - 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 "data.sameRelationMultiUseDummy.id" should be equal to "/same_relation_multi_use_dummies/2" - And the JSON node "data.sameRelationMultiUseDummy.name" should be equal to "Dummy #2" - And the JSON node "data.sameRelationMultiUseDummy.manyToOneRelation.id" should not be null - And the JSON node "data.sameRelationMultiUseDummy.manyToOneRelation.name" should be equal to "RelatedMultiUsedDummy #2" - And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges" should have 2 element - And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges[1].node.id" should not be null - And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges[0].node.name" should be equal to "RelatedManyToManyDummy12" - And the JSON node "data.sameRelationMultiUseDummy.manyToManyRelations.edges[1].node.name" should be equal to "RelatedManyToManyDummy22" - And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges" should have 3 element - And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges[1].node.id" should not be null - And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges[0].node.name" should be equal to "RelatedOneToManyDummy12" - And the JSON node "data.sameRelationMultiUseDummy.oneToManyRelations.edges[2].node.name" should be equal to "RelatedOneToManyDummy32" diff --git a/src/Doctrine/Common/State/LinksHandlerTrait.php b/src/Doctrine/Common/State/LinksHandlerTrait.php index 65aaf3ab447..38f52beba68 100644 --- a/src/Doctrine/Common/State/LinksHandlerTrait.php +++ b/src/Doctrine/Common/State/LinksHandlerTrait.php @@ -35,15 +35,19 @@ private function getLinks(string $resourceClass, Operation $operation, array $co } $newLink = null; - $linkProperty = $context['linkProperty']; + $linkProperty = $context['linkProperty'] ?? null; foreach ($links as $link) { - if (($linkClass === $link->getFromClass()) && ($linkProperty === $link->getFromProperty())) { + if ($linkClass === $link->getFromClass() && $linkProperty === $link->getFromProperty()) { $newLink = $link; break; } } + if ($newLink) { + return [$newLink]; + } + // Using GraphQL, it's possible that we won't find a GraphQL Operation of the same type (e.g. it is disabled). try { $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($linkClass); @@ -64,13 +68,13 @@ private function getLinks(string $resourceClass, Operation $operation, array $co } foreach ($this->getOperationLinks($linkedOperation ?? null) as $link) { - if (($resourceClass === $link->getToClass()) && ($linkProperty === $link->getFromProperty())) { + if ($resourceClass === $link->getToClass() && $linkProperty === $link->getFromProperty()) { $newLink = $link; break; } } - if (null === $newLink) { + if (!$newLink) { throw new RuntimeException(sprintf('The class "%s" cannot be retrieved from "%s".', $resourceClass, $linkClass)); } diff --git a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php index 37689e7be50..d2f3afcae72 100644 --- a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php @@ -68,18 +68,18 @@ private function mergeLinks(array $links, array $toMergeLinks): array { $classLinks = []; foreach ($links as $link) { - $classLinks[$link->getToClass()][$link->getFromProperty()] = $link; + $classLinks[$link->getToClass().'#'.$link->getFromProperty()] = $link; } foreach ($toMergeLinks as $link) { - if (null !== $prevLink = $classLinks[$link->getToClass()][$link->getFromProperty()] ?? null) { - $classLinks[$link->getToClass()][$link->getFromProperty()] = $prevLink->withLink($link); + if (null !== $prevLink = $classLinks[$link->getToClass().'#'.$link->getFromProperty()] ?? null) { + $classLinks[$link->getToClass().'#'.$link->getFromProperty()] = $prevLink->withLink($link); continue; } - $classLinks[$link->getToClass()][$link->getFromProperty()] = $link; + $classLinks[$link->getToClass().'#'.$link->getFromProperty()] = $link; } - return array_values(array_merge(...array_values($classLinks))); + return array_values($classLinks); } } diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php index 023c3356ea8..7237a47359e 100644 --- a/tests/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -63,6 +63,8 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Document\InitializeInput as InitializeInputDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\IriOnlyDummy as IriOnlyDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\MaxDepthDummy as MaxDepthDummyDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\MultiRelationsDummy as MultiRelationsDummyDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\MultiRelationsRelatedDummy as MultiRelationsRelatedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\NetworkPathDummy as NetworkPathDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\NetworkPathRelationDummy as NetworkPathRelationDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Order as OrderDocument; @@ -78,10 +80,8 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedOwnedDummy as RelatedOwnedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedOwningDummy as RelatedOwningDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedSecuredDummy as RelatedSecuredDummyDocument; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedMultiUsedDummy as RelatedMultiUsedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedToDummyFriend as RelatedToDummyFriendDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelationEmbedder as RelationEmbedderDocument; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\SameRelationMultiUseDummy as SameRelationMultiUseDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\SecuredDummy as SecuredDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\SoMany as SoManyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Taxon as TaxonDocument; @@ -140,6 +140,8 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\InternalUser; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\IriOnlyDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsDummy; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsRelatedDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\NetworkPathDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\NetworkPathRelationDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Order; @@ -154,13 +156,11 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RamseyUuidDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedMultiUsedDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwnedDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwningDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedSecuredDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedToDummyFriend; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelationEmbedder; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SameRelationMultiUseDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Site; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SoMany; @@ -764,29 +764,29 @@ public function thereAreDummyObjectsWithRelatedDummies(int $nb, int $nbrelated): } /** - * @Given there are :nb sameRelationMultiUseDummy objects having a manyToOneRelation and each :nbmtmr manyToManyRelations and having each :nbotmr oneToManyRelations + * @Given there are :nb multiRelationsDummy objects having each a manyToOneRelation, :nbmtmr manyToManyRelations and :nbotmr oneToManyRelations */ - public function thereAreSameRelationMultiUseDummyObjectsHavingAManyToOneRelationAndEachManyToManyRelationsAndHavingEachOneToManyRelations(int $nb, int $nbmtmr, int $nbotmr): void + public function thereAreMultiRelationsDummyObjectsHavingEachAManyToOneRelationManyToManyRelationsAndOneToManyRelations(int $nb, int $nbmtmr, int $nbotmr): void { for ($i = 1; $i <= $nb; ++$i) { - $relatedDummy = $this->buildRelatedMultiUsedDummy(); - $relatedDummy->setName('RelatedMultiUsedDummy #'.$i); + $relatedDummy = $this->buildMultiRelationsRelatedDummy(); + $relatedDummy->name = 'RelatedManyToOneDummy #'.$i; - $dummy = $this->buildSameRelationMultiUseDummy(); - $dummy->setName('Dummy #'.$i); + $dummy = $this->buildMultiRelationsDummy(); + $dummy->name = 'Dummy #'.$i; $dummy->setManyToOneRelation($relatedDummy); for ($j = 1; $j <= $nbmtmr; ++$j) { - $manyToManyItem = $this->buildRelatedMultiUsedDummy(); - $manyToManyItem->setName('RelatedManyToManyDummy'.$j.$i); + $manyToManyItem = $this->buildMultiRelationsRelatedDummy(); + $manyToManyItem->name = 'RelatedManyToManyDummy'.$j.$i; $this->manager->persist($manyToManyItem); $dummy->addManyToManyRelation($manyToManyItem); } for ($j = 1; $j <= $nbotmr; ++$j) { - $oneToManyItem = $this->buildRelatedMultiUsedDummy(); - $oneToManyItem->setName('RelatedOneToManyDummy'.$j.$i); + $oneToManyItem = $this->buildMultiRelationsRelatedDummy(); + $oneToManyItem->name = 'RelatedOneToManyDummy'.$j.$i; $oneToManyItem->setOneToManyRelation($dummy); $this->manager->persist($oneToManyItem); @@ -795,7 +795,6 @@ public function thereAreSameRelationMultiUseDummyObjectsHavingAManyToOneRelation $this->manager->persist($relatedDummy); $this->manager->persist($dummy); - } $this->manager->flush(); } @@ -2342,13 +2341,13 @@ private function buildPayment(string $amount): Payment|PaymentDocument return $this->isOrm() ? new Payment($amount) : new PaymentDocument($amount); } - private function buildSameRelationMultiUseDummy(): SameRelationMultiUseDummy|SameRelationMultiUseDummyDocument + private function buildMultiRelationsDummy(): MultiRelationsDummy|MultiRelationsDummyDocument { - return $this->isOrm() ? new SameRelationMultiUseDummy() : new SameRelationMultiUseDummyDocument(); + return $this->isOrm() ? new MultiRelationsDummy() : new MultiRelationsDummyDocument(); } - private function buildRelatedMultiUsedDummy(): RelatedMultiUsedDummy|RelatedMultiUsedDummyDocument + private function buildMultiRelationsRelatedDummy(): MultiRelationsRelatedDummy|MultiRelationsRelatedDummyDocument { - return $this->isOrm() ? new RelatedMultiUsedDummy() : new RelatedMultiUsedDummyDocument(); + return $this->isOrm() ? new MultiRelationsRelatedDummy() : new MultiRelationsRelatedDummyDocument(); } } diff --git a/tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php b/tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php new file mode 100644 index 00000000000..dd70130d1a9 --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/MultiRelationsDummy.php @@ -0,0 +1,79 @@ + + * + * 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\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; + +/** + * Dummy using different kind of relations to the same resource. + * + * @author Thomas Helmrich + */ +#[ApiResource(graphQlOperations: [new QueryCollection(), new Query()])] +#[ODM\Document] +class MultiRelationsDummy +{ + #[ODM\Id(strategy: 'INCREMENT', type: 'int')] + private ?int $id = null; + + #[ODM\Field(type: 'string')] + public string $name; + + #[ODM\ReferenceOne(targetDocument: MultiRelationsRelatedDummy::class, storeAs: 'id', nullable: true)] + public ?MultiRelationsRelatedDummy $manyToOneRelation = null; + + /** @var Collection */ + #[ODM\ReferenceMany(targetDocument: MultiRelationsRelatedDummy::class, storeAs: 'id', nullable: true)] + public Collection $manyToManyRelations; + + /** @var Collection */ + #[ODM\ReferenceMany(targetDocument: MultiRelationsRelatedDummy::class, mappedBy: 'oneToManyRelation', storeAs: 'id')] + public Collection $oneToManyRelations; + + public function __construct() + { + $this->manyToManyRelations = new ArrayCollection(); + $this->oneToManyRelations = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getManyToOneRelation(): ?MultiRelationsRelatedDummy + { + return $this->manyToOneRelation; + } + + public function setManyToOneRelation(?MultiRelationsRelatedDummy $relatedMultiUsedDummy): void + { + $this->manyToOneRelation = $relatedMultiUsedDummy; + } + + public function addManyToManyRelation(MultiRelationsRelatedDummy $relatedMultiUsedDummy): void + { + $this->manyToManyRelations->add($relatedMultiUsedDummy); + } + + public function addOneToManyRelation(MultiRelationsRelatedDummy $relatedMultiUsedDummy): void + { + $this->oneToManyRelations->add($relatedMultiUsedDummy); + } +} diff --git a/tests/Fixtures/TestBundle/Document/MultiRelationsRelatedDummy.php b/tests/Fixtures/TestBundle/Document/MultiRelationsRelatedDummy.php new file mode 100644 index 00000000000..8794d5f4867 --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/MultiRelationsRelatedDummy.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 QueryCollection(), new Query()])] +#[ODM\Document] +class MultiRelationsRelatedDummy +{ + #[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: 'oneToManyRelations', nullable: true, storeAs: 'id')] + private ?MultiRelationsDummy $oneToManyRelation; + + public function getId(): ?int + { + return $this->id; + } + + public function getOneToManyRelation(): ?MultiRelationsDummy + { + return $this->oneToManyRelation; + } + + public function setOneToManyRelation(?MultiRelationsDummy $oneToManyRelation): void + { + $this->oneToManyRelation = $oneToManyRelation; + } +} diff --git a/tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php b/tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php deleted file mode 100644 index c63b7801dfc..00000000000 --- a/tests/Fixtures/TestBundle/Document/RelatedMultiUsedDummy.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * 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\ApiProperty; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\GraphQl\QueryCollection; -use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; - -/** - * Related Multi Used Dummy. - * - * @author Thomas Helmrich - */ -#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] -#[ODM\Document] -class RelatedMultiUsedDummy -{ - #[ApiProperty(writable: false)] - #[ODM\Id(strategy: 'INCREMENT', type: 'int')] - private $id; - - /** - * @var string|null A name - */ - #[ODM\Field(type: 'string', nullable: true)] - public $name; - - #[ODM\ReferenceOne(targetDocument: SameRelationMultiUseDummy::class, inversedBy: 'oneToManyRelations', nullable: true, storeAs: 'id')] - protected ?SameRelationMultiUseDummy $oneToManyRelation = null; - - public function __construct() - { - } - - public function getId() - { - return $this->id; - } - - public function setId($id): void - { - $this->id = $id; - } - - public function setName($name): void - { - $this->name = $name; - } - - public function getName() - { - return $this->name; - } - - public function getOneToManyRelation(): ?SameRelationMultiUseDummy - { - return $this->oneToManyRelation; - } - - public function setOneToManyRelation(SameRelationMultiUseDummy $oneToManyRelation): void - { - $this->oneToManyRelation = $oneToManyRelation; - } -} diff --git a/tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php b/tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php deleted file mode 100644 index 87c66abb908..00000000000 --- a/tests/Fixtures/TestBundle/Document/SameRelationMultiUseDummy.php +++ /dev/null @@ -1,102 +0,0 @@ - - * - * 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\ApiProperty; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\GraphQl\QueryCollection; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; -use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; -use Symfony\Component\Validator\Constraints as Assert; - -/** - * Same relation Multi Use Dummy. - * - * @author Thomas Helmrich - */ -#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] -#[ODM\Document] -class SameRelationMultiUseDummy -{ - /** - * @var int|null The id - */ - #[ODM\Id(strategy: 'INCREMENT', type: 'int')] - private $id; - - /** - * @var string The same relation multi use dummy name - */ - #[ApiProperty(iris: ['https://schema.org/name'])] - #[ODM\Field(type: 'string')] - #[Assert\NotBlank] - private string $name; - - #[ODM\ReferenceOne(targetDocument: RelatedMultiUsedDummy::class, storeAs: 'id', nullable: true)] - public ?RelatedMultiUsedDummy $manyToOneRelation = null; - - #[ODM\ReferenceMany(targetDocument: RelatedMultiUsedDummy::class, storeAs: 'id', nullable: true)] - public Collection|iterable $manyToManyRelations; - - #[ODM\ReferenceMany(targetDocument: RelatedMultiUsedDummy::class, mappedBy: 'oneToManyRelation', storeAs: 'id')] - public Collection|iterable $oneToManyRelations; - - public function __construct() - { - $this->manyToManyRelations = new ArrayCollection(); - $this->oneToManyRelations = new ArrayCollection(); - } - - public function getId() - { - return $this->id; - } - - public function setId($id): void - { - $this->id = $id; - } - - public function setName(string $name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function getManyToOneRelation(): ?RelatedMultiUsedDummy - { - return $this->manyToOneRelation; - } - - public function setManyToOneRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void - { - $this->manyToOneRelation = $relatedMultiUsedDummy; - } - - public function addManyToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void - { - $this->manyToManyRelations->add($relatedMultiUsedDummy); - } - - public function addOneToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void - { - $this->oneToManyRelations->add($relatedMultiUsedDummy); - } -} diff --git a/tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php b/tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php new file mode 100644 index 00000000000..14e9d8a0f8b --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/MultiRelationsDummy.php @@ -0,0 +1,81 @@ + + * + * 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\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; + +/** + * Dummy using different kind of relations to the same resource. + * + * @author Thomas Helmrich + */ +#[ApiResource(graphQlOperations: [new QueryCollection(), new Query()])] +#[ORM\Entity] +class MultiRelationsDummy +{ + #[ORM\Column(type: 'integer', nullable: true)] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] + private ?int $id = null; + + #[ORM\Column] + public string $name; + + #[ORM\ManyToOne(targetEntity: MultiRelationsRelatedDummy::class)] + public ?MultiRelationsRelatedDummy $manyToOneRelation = null; + + /** @var Collection */ + #[ORM\ManyToMany(targetEntity: MultiRelationsRelatedDummy::class)] + public Collection $manyToManyRelations; + + /** @var Collection */ + #[ORM\OneToMany(targetEntity: MultiRelationsRelatedDummy::class, mappedBy: 'oneToManyRelation')] + public Collection $oneToManyRelations; + + public function __construct() + { + $this->manyToManyRelations = new ArrayCollection(); + $this->oneToManyRelations = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getManyToOneRelation(): ?MultiRelationsRelatedDummy + { + return $this->manyToOneRelation; + } + + public function setManyToOneRelation(?MultiRelationsRelatedDummy $relatedMultiUsedDummy): void + { + $this->manyToOneRelation = $relatedMultiUsedDummy; + } + + public function addManyToManyRelation(MultiRelationsRelatedDummy $relatedMultiUsedDummy): void + { + $this->manyToManyRelations->add($relatedMultiUsedDummy); + } + + public function addOneToManyRelation(MultiRelationsRelatedDummy $relatedMultiUsedDummy): void + { + $this->oneToManyRelations->add($relatedMultiUsedDummy); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/MultiRelationsRelatedDummy.php b/tests/Fixtures/TestBundle/Entity/MultiRelationsRelatedDummy.php new file mode 100644 index 00000000000..ba462ce4b56 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/MultiRelationsRelatedDummy.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 QueryCollection(), new Query()])] +#[ORM\Entity] +class MultiRelationsRelatedDummy +{ + #[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: 'oneToManyRelations')] + private ?MultiRelationsDummy $oneToManyRelation = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getOneToManyRelation(): ?MultiRelationsDummy + { + return $this->oneToManyRelation; + } + + public function setOneToManyRelation(?MultiRelationsDummy $oneToManyRelation): void + { + $this->oneToManyRelation = $oneToManyRelation; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php deleted file mode 100644 index f64b7177878..00000000000 --- a/tests/Fixtures/TestBundle/Entity/RelatedMultiUsedDummy.php +++ /dev/null @@ -1,79 +0,0 @@ - - * - * 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\ApiProperty; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\GraphQl\QueryCollection; -use Doctrine\ORM\Mapping as ORM; - -/** - * Related Multi Used Dummy. - * - * @author Thomas Helmrich - */ -#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] -#[ORM\Entity] -class RelatedMultiUsedDummy -{ - #[ApiProperty(writable: false)] - #[ORM\Column(type: 'integer')] - #[ORM\Id] - #[ORM\GeneratedValue(strategy: 'AUTO')] - private $id; - - /** - * @var string|null A name - */ - #[ORM\Column(nullable: true)] - public $name; - - #[ORM\ManyToOne(targetEntity: SameRelationMultiUseDummy::class, inversedBy: 'oneToManyRelations')] - protected ?SameRelationMultiUseDummy $oneToManyRelation = null; - - public function __construct() - { - } - - public function getId() - { - return $this->id; - } - - public function setId($id): void - { - $this->id = $id; - } - - public function setName($name): void - { - $this->name = $name; - } - - public function getName() - { - return $this->name; - } - - public function getOneToManyRelation(): ?SameRelationMultiUseDummy - { - return $this->oneToManyRelation; - } - - public function setOneToManyRelation(SameRelationMultiUseDummy $oneToManyRelation): void - { - $this->oneToManyRelation = $oneToManyRelation; - } -} diff --git a/tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php b/tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php deleted file mode 100644 index b11ffa016ff..00000000000 --- a/tests/Fixtures/TestBundle/Entity/SameRelationMultiUseDummy.php +++ /dev/null @@ -1,106 +0,0 @@ - - * - * 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\ApiProperty; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\GraphQl\QueryCollection; -use ApiPlatform\Metadata\Link; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; -use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Validator\Constraints as Assert; - -/** - * Same relation Multi Use Dummy. - * - * @author Thomas Helmrich - */ -#[ApiResource(graphQlOperations: [new QueryCollection(name: 'collection_query'), new Query(name: 'item_query')])] -#[ORM\Entity] -class SameRelationMultiUseDummy -{ - /** - * @var int|null The id - */ - #[ORM\Column(type: 'integer', nullable: true)] - #[ORM\Id] - #[ORM\GeneratedValue(strategy: 'AUTO')] - private $id; - - /** - * @var string The same relation multi use dummy name - */ - #[ApiProperty(iris: ['https://schema.org/name'])] - #[ORM\Column] - #[Assert\NotBlank] - private string $name; - - #[ORM\ManyToOne(targetEntity: RelatedMultiUsedDummy::class)] - public ?RelatedMultiUsedDummy $manyToOneRelation = null; - - #[ORM\ManyToMany(targetEntity: RelatedMultiUsedDummy::class)] - public Collection|iterable $manyToManyRelations; - - #[ORM\OneToMany(targetEntity: RelatedMultiUsedDummy::class, mappedBy: 'oneToManyRelation')] - public Collection|iterable $oneToManyRelations; - - public function __construct() - { - $this->manyToManyRelations = new ArrayCollection(); - $this->oneToManyRelations = new ArrayCollection(); - } - - public function getId() - { - return $this->id; - } - - public function setId($id): void - { - $this->id = $id; - } - - public function setName(string $name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function getManyToOneRelation(): ?RelatedMultiUsedDummy - { - return $this->manyToOneRelation; - } - - public function setManyToOneRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void - { - $this->manyToOneRelation = $relatedMultiUsedDummy; - } - - public function addManyToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void - { - $this->manyToManyRelations->add($relatedMultiUsedDummy); - } - - public function addOneToManyRelation(RelatedMultiUsedDummy $relatedMultiUsedDummy): void - { - $this->oneToManyRelations->add($relatedMultiUsedDummy); - } -} diff --git a/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php b/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php index efd62bccb5f..0355d49cafb 100644 --- a/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php +++ b/tests/Fixtures/TestBundle/State/DummyDtoInputOutputProvider.php @@ -19,7 +19,6 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Dto\Document\OutputDto as OutputDtoDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyDtoInputOutput; -use Doctrine\ORM\PersistentCollection; final class DummyDtoInputOutputProvider implements ProviderInterface {