From 92723fbf4a6417899c9a0fb4343a5ce5d46fdc1c Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Fri, 9 Aug 2019 16:29:43 +0200 Subject: [PATCH] Use correct resource configuration for filter args of nested collection --- features/graphql/filters.feature | 24 ++++- src/GraphQl/Type/SchemaBuilder.php | 96 +++++++++++-------- .../TestBundle/Document/DummyCarColor.php | 3 + .../TestBundle/Entity/DummyCarColor.php | 3 + tests/GraphQl/Type/SchemaBuilderTest.php | 1 + 5 files changed, 84 insertions(+), 43 deletions(-) diff --git a/features/graphql/filters.feature b/features/graphql/filters.feature index 17f4c6bd129..bf62059fba8 100644 --- a/features/graphql/filters.feature +++ b/features/graphql/filters.feature @@ -107,11 +107,33 @@ Feature: Collections filtering } } """ - And the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 0 elements + Then the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 0 elements And the JSON node "data.dummies.edges[1].node.relatedDummies.edges" should have 0 elements And the JSON node "data.dummies.edges[2].node.relatedDummies.edges" should have 1 element And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy13" + @createSchema + Scenario: Use a filter of a nested collection + Given there is a DummyCar entity with related colors + When I send the following GraphQL request: + """ + { + dummyCar(id: "/dummy_cars/1") { + id + colors(prop: "blue") { + edges { + node { + id + prop + } + } + } + } + } + """ + Then the JSON node "data.dummyCar.colors.edges" should have 1 element + And the JSON node "data.dummyCar.colors.edges[0].node.prop" should be equal to "blue" + @createSchema Scenario: Retrieve a collection filtered using the related search filter Given there are 1 dummy objects having each 2 relatedDummies diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 06b2928173e..8f5b78f78ee 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -168,12 +168,12 @@ private function getQueryFields(string $resourceClass, ResourceMetadata $resourc $shortName = $resourceMetadata->getShortName(); $deprecationReason = $resourceMetadata->getGraphqlAttribute('query', 'deprecation_reason', '', true); - if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass)) { + if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass)) { $fieldConfiguration['args'] += ['id' => ['type' => GraphQLType::id()]]; $queryFields[lcfirst($shortName)] = $fieldConfiguration; } - if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass)) { + if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass)) { $queryFields[lcfirst(Inflector::pluralize($shortName))] = $fieldConfiguration; } @@ -189,8 +189,8 @@ private function getMutationFields(string $resourceClass, ResourceMetadata $reso $resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass); $deprecationReason = $resourceMetadata->getGraphqlAttribute($mutationName, 'deprecation_reason', '', true); - if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, ucfirst("{$mutationName}s a $shortName."), $deprecationReason, $resourceType, $resourceClass, false, $mutationName)) { - $fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, $resourceType, $resourceClass, true, $mutationName)]; + if ($fieldConfiguration = $this->getResourceFieldConfiguration(ucfirst("{$mutationName}s a $shortName."), $deprecationReason, $resourceType, $resourceClass, false, $mutationName)) { + $fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, $deprecationReason, $resourceType, $resourceClass, true, $mutationName)]; if (!$this->isCollection($resourceType)) { $itemMutationResolverFactory = $this->itemMutationResolverFactory; @@ -206,9 +206,11 @@ private function getMutationFields(string $resourceClass, ResourceMetadata $reso * * @see http://webonyx.github.io/graphql-php/type-system/object-types/ */ - private function getResourceFieldConfiguration(string $resourceClass, ResourceMetadata $resourceMetadata, ?string $fieldDescription, string $deprecationReason, Type $type, string $rootResource, bool $input = false, string $mutationName = null, int $depth = 0): ?array + private function getResourceFieldConfiguration(?string $fieldDescription, string $deprecationReason, Type $type, string $rootResource, bool $input = false, string $mutationName = null, int $depth = 0): ?array { try { + $resourceClass = $this->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName(); + if (null === $graphqlType = $this->convertType($type, $input, $mutationName, $depth)) { return null; } @@ -216,9 +218,15 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe $graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType() : $graphqlType; $isStandardGraphqlType = \in_array($graphqlWrappedType, GraphQLType::getStandardTypes(), true); if ($isStandardGraphqlType) { - $className = ''; - } else { - $className = $this->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName(); + $resourceClass = ''; + } + + $resourceMetadata = null; + if (!empty($resourceClass)) { + try { + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + } catch (ResourceClassNotFoundException $e) { + } } $args = []; @@ -236,39 +244,14 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe ]; } - foreach ($resourceMetadata->getGraphqlAttribute('query', 'filters', [], true) as $filterId) { - if (null === $this->filterLocator || !$this->filterLocator->has($filterId)) { - continue; - } - - foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) { - $nullable = isset($value['required']) ? !$value['required'] : true; - $filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']); - $graphqlFilterType = $this->convertType($filterType, false, null, $depth); - - if ('[]' === substr($key, -2)) { - $graphqlFilterType = GraphQLType::listOf($graphqlFilterType); - $key = substr($key, 0, -2).'_list'; - } - - parse_str($key, $parsed); - if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) { - $parsed = [$key => '']; - } - array_walk_recursive($parsed, function (&$value) use ($graphqlFilterType) { - $value = $graphqlFilterType; - }); - $args = $this->mergeFilterArgs($args, $parsed, $resourceMetadata, $key); - } - } - $args = $this->convertFilterArgsToTypes($args); + $args = $this->getFilterArgs($args, $resourceClass, $resourceMetadata, $depth); } if ($isStandardGraphqlType || $input) { $resolve = null; } elseif ($this->isCollection($type)) { $resolverFactory = $this->collectionResolverFactory; - $resolve = $resolverFactory($className, $rootResource, $mutationName); + $resolve = $resolverFactory($resourceClass, $rootResource, $mutationName); } else { $resolve = $this->itemResolver; } @@ -287,6 +270,41 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe return null; } + private function getFilterArgs(array $args, ?string $resourceClass, ?ResourceMetadata $resourceMetadata, int $depth): array + { + if (null === $resourceMetadata || null === $resourceClass) { + return $args; + } + + foreach ($resourceMetadata->getGraphqlAttribute('query', 'filters', [], true) as $filterId) { + if (null === $this->filterLocator || !$this->filterLocator->has($filterId)) { + continue; + } + + foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) { + $nullable = isset($value['required']) ? !$value['required'] : true; + $filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']); + $graphqlFilterType = $this->convertType($filterType, false, null, $depth); + + if ('[]' === substr($key, -2)) { + $graphqlFilterType = GraphQLType::listOf($graphqlFilterType); + $key = substr($key, 0, -2).'_list'; + } + + parse_str($key, $parsed); + if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) { + $parsed = [$key => '']; + } + array_walk_recursive($parsed, function (&$value) use ($graphqlFilterType) { + $value = $graphqlFilterType; + }); + $args = $this->mergeFilterArgs($args, $parsed, $resourceMetadata, $key); + } + } + + return $this->convertFilterArgsToTypes($args); + } + private function mergeFilterArgs(array $args, array $parsed, ResourceMetadata $resourceMetadata = null, $original = ''): array { foreach ($parsed as $key => $value) { @@ -508,15 +526,9 @@ private function getResourceObjectTypeFields(?string $resourceClass, ResourceMet continue; } - $rootResource = $resourceClass; - if (null !== $propertyMetadata->getSubresource()) { - $resourceClass = $propertyMetadata->getSubresource()->getResourceClass(); - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - } - if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, $propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', ''), $propertyType, $rootResource, $input, $mutationName, $depth)) { + if ($fieldConfiguration = $this->getResourceFieldConfiguration($propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', ''), $propertyType, $resourceClass, $input, $mutationName, $depth)) { $fields['id' === $property ? '_id' : $property] = $fieldConfiguration; } - $resourceClass = $rootResource; } } diff --git a/tests/Fixtures/TestBundle/Document/DummyCarColor.php b/tests/Fixtures/TestBundle/Document/DummyCarColor.php index ff26a12a376..206132986ad 100644 --- a/tests/Fixtures/TestBundle/Document/DummyCarColor.php +++ b/tests/Fixtures/TestBundle/Document/DummyCarColor.php @@ -13,7 +13,9 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document; +use ApiPlatform\Core\Annotation\ApiFilter; use ApiPlatform\Core\Annotation\ApiResource; +use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\SearchFilter; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Validator\Constraints as Assert; @@ -43,6 +45,7 @@ class DummyCarColor * @var string * * @ODM\Field(nullable=false) + * @ApiFilter(SearchFilter::class) * @Assert\NotBlank * * @Serializer\Groups({"colors"}) diff --git a/tests/Fixtures/TestBundle/Entity/DummyCarColor.php b/tests/Fixtures/TestBundle/Entity/DummyCarColor.php index 445d7f5f04d..a3e1f08cee7 100644 --- a/tests/Fixtures/TestBundle/Entity/DummyCarColor.php +++ b/tests/Fixtures/TestBundle/Entity/DummyCarColor.php @@ -13,7 +13,9 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity; +use ApiPlatform\Core\Annotation\ApiFilter; use ApiPlatform\Core\Annotation\ApiResource; +use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Validator\Constraints as Assert; @@ -46,6 +48,7 @@ class DummyCarColor * @var string * * @ORM\Column(nullable=false) + * @ApiFilter(SearchFilter::class) * @Assert\NotBlank * * @Serializer\Groups({"colors"}) diff --git a/tests/GraphQl/Type/SchemaBuilderTest.php b/tests/GraphQl/Type/SchemaBuilderTest.php index 018904e3451..88074208817 100644 --- a/tests/GraphQl/Type/SchemaBuilderTest.php +++ b/tests/GraphQl/Type/SchemaBuilderTest.php @@ -275,6 +275,7 @@ private function createSchemaBuilder($propertyMetadataMockBuilder, bool $paginat ); $resourceMetadataFactoryProphecy->create($resourceClassName)->willReturn($resourceMetadata); $resourceMetadataFactoryProphecy->create('unknownResource')->willThrow(new ResourceClassNotFoundException()); + $resourceMetadataFactoryProphecy->create('DateTime')->willThrow(new ResourceClassNotFoundException()); $propertyNames = []; foreach (Type::$builtinTypes as $builtinType) {