From de3e7aefb01d8458bd7d493a3328fefdd4ac2965 Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Wed, 7 Sep 2022 18:14:24 +0200 Subject: [PATCH 1/8] Fix Graphql collection field with disabled pagination --- src/GraphQl/Type/FieldsBuilder.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php index 8817899c8ef..f0923de38c3 100644 --- a/src/GraphQl/Type/FieldsBuilder.php +++ b/src/GraphQl/Type/FieldsBuilder.php @@ -487,7 +487,10 @@ private function convertType(Type $type, bool $input, Operation $rootOperation, } if ($this->typeBuilder->isCollection($type)) { - return $this->pagination->isGraphQlEnabled($rootOperation) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $rootOperation) : GraphQLType::listOf($graphqlType); + $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); + $resolverOperation = $resourceMetadataCollection->getOperation(null, true); + + return $this->pagination->isGraphQlEnabled($resolverOperation) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $rootOperation) : GraphQLType::listOf($graphqlType); } return $forceNullable || !$graphqlType instanceof NullableType || $type->isNullable() || ($rootOperation instanceof Mutation && 'update' === $rootOperation->getName()) From 21264a9b934a84a521c33a87363b6fa76cf49643 Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Thu, 8 Sep 2022 11:23:15 +0200 Subject: [PATCH 2/8] Fix Graphql fields with same linkClass --- src/Doctrine/Common/State/LinksHandlerTrait.php | 2 +- src/GraphQl/Resolver/Stage/ReadStage.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Doctrine/Common/State/LinksHandlerTrait.php b/src/Doctrine/Common/State/LinksHandlerTrait.php index ccddbc4fa0f..627a464c1c8 100644 --- a/src/Doctrine/Common/State/LinksHandlerTrait.php +++ b/src/Doctrine/Common/State/LinksHandlerTrait.php @@ -62,7 +62,7 @@ private function getLinks(string $resourceClass, Operation $operation, array $co } foreach ($this->getOperationLinks($linkedOperation ?? null) as $link) { - if ($resourceClass === $link->getToClass()) { + if (($resourceClass === $link->getToClass()) && ($context['linkProperty'] === $link->getFromProperty())) { $newLinks[] = $link; } } diff --git a/src/GraphQl/Resolver/Stage/ReadStage.php b/src/GraphQl/Resolver/Stage/ReadStage.php index 8c0742f5b8e..3f3788e3abf 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); From 468732bc79cd2cae104dfa2f779c22bb3a99baa4 Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Thu, 8 Sep 2022 11:38:15 +0200 Subject: [PATCH 3/8] Fix Graphql fields with same linkClass --- .../Factory/LinkResourceMetadataCollectionFactory.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php index d8106575581..e49de3e52ae 100644 --- a/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/LinkResourceMetadataCollectionFactory.php @@ -66,18 +66,18 @@ 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))); } } From 984db69a240323847df5711283a10ef885c04d8a Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Thu, 8 Sep 2022 12:38:36 +0200 Subject: [PATCH 4/8] fix rest & graphql operation interference --- src/GraphQl/Type/FieldsBuilder.php | 2 +- .../Resource/ResourceMetadataCollection.php | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php index f0923de38c3..4071afc1c69 100644 --- a/src/GraphQl/Type/FieldsBuilder.php +++ b/src/GraphQl/Type/FieldsBuilder.php @@ -488,7 +488,7 @@ private function convertType(Type $type, bool $input, Operation $rootOperation, if ($this->typeBuilder->isCollection($type)) { $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $resolverOperation = $resourceMetadataCollection->getOperation(null, true); + $resolverOperation = $resourceMetadataCollection->getOperation(null, true, false, true); return $this->pagination->isGraphQlEnabled($resolverOperation) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $rootOperation) : GraphQLType::listOf($graphqlType); } diff --git a/src/Metadata/Resource/ResourceMetadataCollection.php b/src/Metadata/Resource/ResourceMetadataCollection.php index dff15ee6bb7..52da5146542 100644 --- a/src/Metadata/Resource/ResourceMetadataCollection.php +++ b/src/Metadata/Resource/ResourceMetadataCollection.php @@ -33,7 +33,7 @@ public function __construct(private readonly string $resourceClass, array $input parent::__construct($input); } - public function getOperation(?string $operationName = null, bool $forceCollection = false, bool $httpOperation = false): Operation + public function getOperation(?string $operationName = null, bool $forceCollection = false, bool $httpOperation = false, bool $priorizeGraphQl = false): Operation { $operationName ??= ''; if (isset($this->operationCache[$operationName])) { @@ -51,6 +51,19 @@ public function getOperation(?string $operationName = null, bool $forceCollectio /** @var ApiResource $metadata */ $metadata = $it->current(); + if ($priorizeGraphQl) { + foreach ($metadata->getGraphQlOperations() ?? [] as $name => $operation) { + $isCollection = $operation instanceof CollectionOperationInterface; + if ('' === $operationName && ($forceCollection ? $isCollection : !$isCollection) && false === $httpOperation) { + return $this->operationCache['graphql_'.$operationName] = $operation; + } + + if ($name === $operationName) { + return $this->operationCache['graphql_'.$operationName] = $operation; + } + } + } + foreach ($metadata->getOperations() ?? [] as $name => $operation) { $isCollection = $operation instanceof CollectionOperationInterface; if ('' === $operationName && \in_array($operation->getMethod() ?? HttpOperation::METHOD_GET, [HttpOperation::METHOD_GET, HttpOperation::METHOD_OPTIONS, HttpOperation::METHOD_HEAD], true) && ($forceCollection ? $isCollection : !$isCollection)) { @@ -66,14 +79,16 @@ public function getOperation(?string $operationName = null, bool $forceCollectio } } - foreach ($metadata->getGraphQlOperations() ?? [] as $name => $operation) { - $isCollection = $operation instanceof CollectionOperationInterface; - if ('' === $operationName && ($forceCollection ? $isCollection : !$isCollection) && false === $httpOperation) { - return $this->operationCache['graphql_'.$operationName] = $operation; - } + if (!$priorizeGraphQl) { + foreach ($metadata->getGraphQlOperations() ?? [] as $name => $operation) { + $isCollection = $operation instanceof CollectionOperationInterface; + if ('' === $operationName && ($forceCollection ? $isCollection : !$isCollection) && false === $httpOperation) { + return $this->operationCache['graphql_'.$operationName] = $operation; + } - if ($name === $operationName) { - return $this->operationCache['graphql_'.$operationName] = $operation; + if ($name === $operationName) { + return $this->operationCache['graphql_'.$operationName] = $operation; + } } } From 3b75b187968139bc429e5e214cc2038f22e1f5ba Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Thu, 8 Sep 2022 15:00:59 +0200 Subject: [PATCH 5/8] Fix Graphql fields with same linkClass --- src/Doctrine/Common/State/LinksHandlerTrait.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Doctrine/Common/State/LinksHandlerTrait.php b/src/Doctrine/Common/State/LinksHandlerTrait.php index 627a464c1c8..80d222694d3 100644 --- a/src/Doctrine/Common/State/LinksHandlerTrait.php +++ b/src/Doctrine/Common/State/LinksHandlerTrait.php @@ -35,10 +35,11 @@ private function getLinks(string $resourceClass, Operation $operation, array $co } $newLinks = []; + $linkProperty = $context['linkProperty']; foreach ($links as $link) { - if ($linkClass === $link->getFromClass()) { - $newLinks[] = $link; + if (($linkClass === $link->getFromClass()) && ($linkProperty === $link->getFromProperty())) { + $newLinks[$linkClass][$linkProperty] = $link; } } @@ -62,16 +63,16 @@ private function getLinks(string $resourceClass, Operation $operation, array $co } foreach ($this->getOperationLinks($linkedOperation ?? null) as $link) { - if (($resourceClass === $link->getToClass()) && ($context['linkProperty'] === $link->getFromProperty())) { - $newLinks[] = $link; + if (($resourceClass === $link->getToClass()) && ($linkProperty === $link->getFromProperty())) { + $newLinks[$linkClass][$linkProperty] = $link; } } - if (!$newLinks) { + if ([] === $newLinks) { throw new RuntimeException(sprintf('The class "%s" cannot be retrieved from "%s".', $resourceClass, $linkClass)); } - return $newLinks; + return array_values(array_merge(...array_values($newLinks))); } private function getIdentifierValue(array &$identifiers, string $name = null): mixed From 5fd5a5b9890c43e9589c97b7f60afcf815c4a7e7 Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Thu, 8 Sep 2022 15:53:40 +0200 Subject: [PATCH 6/8] improvement --- .../Resource/ResourceMetadataCollection.php | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/src/Metadata/Resource/ResourceMetadataCollection.php b/src/Metadata/Resource/ResourceMetadataCollection.php index 52da5146542..16fa22ab542 100644 --- a/src/Metadata/Resource/ResourceMetadataCollection.php +++ b/src/Metadata/Resource/ResourceMetadataCollection.php @@ -18,6 +18,7 @@ use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Operations; /** * @experimental @@ -51,44 +52,21 @@ public function getOperation(?string $operationName = null, bool $forceCollectio /** @var ApiResource $metadata */ $metadata = $it->current(); - if ($priorizeGraphQl) { - foreach ($metadata->getGraphQlOperations() ?? [] as $name => $operation) { - $isCollection = $operation instanceof CollectionOperationInterface; - if ('' === $operationName && ($forceCollection ? $isCollection : !$isCollection) && false === $httpOperation) { - return $this->operationCache['graphql_'.$operationName] = $operation; - } - - if ($name === $operationName) { - return $this->operationCache['graphql_'.$operationName] = $operation; - } + if ($priorizeGraphQl && ([] !== $graphQlOperations = $metadata->getGraphQlOperations())) { + if (null !== $graphQlOperation = $this->findGraphQlOperation($graphQlOperations, $operationName, $forceCollection, $httpOperation)) { + return $graphQlOperation; } } - foreach ($metadata->getOperations() ?? [] as $name => $operation) { - $isCollection = $operation instanceof CollectionOperationInterface; - if ('' === $operationName && \in_array($operation->getMethod() ?? HttpOperation::METHOD_GET, [HttpOperation::METHOD_GET, HttpOperation::METHOD_OPTIONS, HttpOperation::METHOD_HEAD], true) && ($forceCollection ? $isCollection : !$isCollection)) { - return $this->operationCache[$operationName] = $operation; - } - - if ($name === $operationName) { - return $this->operationCache[$operationName] = $operation; - } - - if ($operation->getUriTemplate() === $operationName) { - return $this->operationCache[$operationName] = $operation; + if (null !== $operations = $metadata->getOperations()) { + if (null !== $operation = $this->findHttpOperation($operations, $operationName, $forceCollection, $httpOperation)) { + return $operation; } } - if (!$priorizeGraphQl) { - foreach ($metadata->getGraphQlOperations() ?? [] as $name => $operation) { - $isCollection = $operation instanceof CollectionOperationInterface; - if ('' === $operationName && ($forceCollection ? $isCollection : !$isCollection) && false === $httpOperation) { - return $this->operationCache['graphql_'.$operationName] = $operation; - } - - if ($name === $operationName) { - return $this->operationCache['graphql_'.$operationName] = $operation; - } + if (!$priorizeGraphQl && ([] !== $graphQlOperations = $metadata->getGraphQlOperations())) { + if (null !== $graphQlOperation = $this->findGraphQlOperation($graphQlOperations, $operationName, $forceCollection, $httpOperation)) { + return $graphQlOperation; } } @@ -103,6 +81,42 @@ public function getOperation(?string $operationName = null, bool $forceCollectio $this->handleNotFound($operationName, $metadata); } + private function findHttpOperation(Operations $operations, string $operationName, bool $forceCollection, bool $httpOperation): ?Operation + { + foreach ($operations as $name => $operation) { + $isCollection = $operation instanceof CollectionOperationInterface; + if ('' === $operationName && \in_array($operation->getMethod() ?? HttpOperation::METHOD_GET, [HttpOperation::METHOD_GET, HttpOperation::METHOD_OPTIONS, HttpOperation::METHOD_HEAD], true) && ($forceCollection ? $isCollection : !$isCollection)) { + return $this->operationCache[$operationName] = $operation; + } + + if ($name === $operationName) { + return $this->operationCache[$operationName] = $operation; + } + + if ($operation->getUriTemplate() === $operationName) { + return $this->operationCache[$operationName] = $operation; + } + } + + return null; + } + + private function findGraphQlOperation(array $operations, string $operationName, bool $forceCollection, bool $httpOperation): ?Operation + { + foreach ($operations as $name => $operation) { + $isCollection = $operation instanceof CollectionOperationInterface; + if ('' === $operationName && ($forceCollection ? $isCollection : !$isCollection) && false === $httpOperation) { + return $this->operationCache['graphql_'.$operationName] = $operation; + } + + if ($name === $operationName) { + return $this->operationCache['graphql_'.$operationName] = $operation; + } + } + + return null; + } + /** * @throws OperationNotFoundException */ From 37ffecc34cd6c90fcea62aafb0b1eb65ffe3fd4b Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Thu, 8 Sep 2022 15:57:47 +0200 Subject: [PATCH 7/8] improvement --- src/Doctrine/Common/State/LinksHandlerTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Doctrine/Common/State/LinksHandlerTrait.php b/src/Doctrine/Common/State/LinksHandlerTrait.php index 80d222694d3..103dbd21b6c 100644 --- a/src/Doctrine/Common/State/LinksHandlerTrait.php +++ b/src/Doctrine/Common/State/LinksHandlerTrait.php @@ -72,7 +72,7 @@ private function getLinks(string $resourceClass, Operation $operation, array $co throw new RuntimeException(sprintf('The class "%s" cannot be retrieved from "%s".', $resourceClass, $linkClass)); } - return array_values(array_merge(...array_values($newLinks))); + return array_values(...array_values($newLinks)); } private function getIdentifierValue(array &$identifiers, string $name = null): mixed From a834672baf1d9c45c79dcb44465837c590303d1f Mon Sep 17 00:00:00 2001 From: David ALLIX Date: Thu, 8 Sep 2022 16:11:45 +0200 Subject: [PATCH 8/8] improvement --- src/Metadata/Resource/ResourceMetadataCollection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Metadata/Resource/ResourceMetadataCollection.php b/src/Metadata/Resource/ResourceMetadataCollection.php index 16fa22ab542..7559ffde94c 100644 --- a/src/Metadata/Resource/ResourceMetadataCollection.php +++ b/src/Metadata/Resource/ResourceMetadataCollection.php @@ -16,6 +16,7 @@ use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operations; @@ -101,7 +102,7 @@ private function findHttpOperation(Operations $operations, string $operationName return null; } - private function findGraphQlOperation(array $operations, string $operationName, bool $forceCollection, bool $httpOperation): ?Operation + private function findGraphQlOperation(array $operations, string $operationName, bool $forceCollection, bool $httpOperation): ?GraphQlOperation { foreach ($operations as $name => $operation) { $isCollection = $operation instanceof CollectionOperationInterface;