From 29f08187c878b10ad4bc1832228338304e0942de Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Mon, 12 Feb 2018 11:17:37 +0100 Subject: [PATCH 1/4] Add a depth variable in SchemaBuilder --- src/GraphQl/Type/SchemaBuilder.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 4267f1ddcc5..70eb19421c4 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -189,10 +189,10 @@ private function getMutationFields(string $resourceClass, ResourceMetadata $reso * * @return array|null */ - private function getResourceFieldConfiguration(string $resourceClass, ResourceMetadata $resourceMetadata, string $fieldDescription = null, Type $type, string $rootResource, bool $input = false, string $mutationName = null) + private function getResourceFieldConfiguration(string $resourceClass, ResourceMetadata $resourceMetadata, string $fieldDescription = null, Type $type, string $rootResource, bool $input = false, string $mutationName = null, int $depth = 0) { try { - if (null === $graphqlType = $this->convertType($type, $input, $mutationName)) { + if (null === $graphqlType = $this->convertType($type, $input, $mutationName, $depth)) { return null; } @@ -227,7 +227,7 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe 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); + $graphqlFilterType = $this->convertType($filterType, false, null, $depth); if ('[]' === $newKey = substr($key, -2)) { $key = $newKey; @@ -317,7 +317,7 @@ private function convertFilterArgsToTypes(array $args): array * * @throws InvalidTypeException */ - private function convertType(Type $type, bool $input = false, string $mutationName = null) + private function convertType(Type $type, bool $input = false, string $mutationName = null, int $depth = 0) { $resourceClass = null; switch ($builtinType = $type->getBuiltinType()) { @@ -357,7 +357,7 @@ private function convertType(Type $type, bool $input = false, string $mutationNa return null; } - $graphqlType = $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $mutationName); + $graphqlType = $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $mutationName, $depth); break; default: throw new InvalidTypeException(sprintf('The type "%s" is not supported.', $builtinType)); @@ -375,7 +375,7 @@ private function convertType(Type $type, bool $input = false, string $mutationNa * * @return ObjectType|InputObjectType */ - private function getResourceObjectType(string $resourceClass, ResourceMetadata $resourceMetadata, bool $input = false, string $mutationName = null): GraphQLType + private function getResourceObjectType(string $resourceClass, ResourceMetadata $resourceMetadata, bool $input = false, string $mutationName = null, int $depth = 0): GraphQLType { if (isset($this->graphqlTypes[$resourceClass][$mutationName][$input])) { return $this->graphqlTypes[$resourceClass][$mutationName][$input]; @@ -395,8 +395,8 @@ private function getResourceObjectType(string $resourceClass, ResourceMetadata $ 'name' => $shortName, 'description' => $resourceMetadata->getDescription(), 'resolveField' => $this->defaultFieldResolver, - 'fields' => function () use ($resourceClass, $resourceMetadata, $input, $mutationName) { - return $this->getResourceObjectTypeFields($resourceClass, $resourceMetadata, $input, $mutationName); + 'fields' => function () use ($resourceClass, $resourceMetadata, $input, $mutationName, $depth) { + return $this->getResourceObjectTypeFields($resourceClass, $resourceMetadata, $input, $mutationName, $depth); }, 'interfaces' => [$this->getNodeInterface()], ]; @@ -407,7 +407,7 @@ private function getResourceObjectType(string $resourceClass, ResourceMetadata $ /** * Gets the fields of the type of the given resource. */ - private function getResourceObjectTypeFields(string $resourceClass, ResourceMetadata $resourceMetadata, bool $input = false, string $mutationName = null): array + private function getResourceObjectTypeFields(string $resourceClass, ResourceMetadata $resourceMetadata, bool $input = false, string $mutationName = null, int $depth = 0): array { $fields = []; $idField = ['type' => GraphQLType::nonNull(GraphQLType::id())]; @@ -434,7 +434,7 @@ private function getResourceObjectTypeFields(string $resourceClass, ResourceMeta continue; } - if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, $propertyMetadata->getDescription(), $propertyType, $resourceClass, $input, $mutationName)) { + if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, $propertyMetadata->getDescription(), $propertyType, $resourceClass, $input, $mutationName, ++$depth)) { $fields['id' === $property ? '_id' : $property] = $fieldConfiguration; } } From 957f30d6acbab00a04e4dac02636f08924425988 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Mon, 12 Feb 2018 11:18:58 +0100 Subject: [PATCH 2/4] Pagination should not be used for input --- src/GraphQl/Type/SchemaBuilder.php | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 70eb19421c4..7fa00c2fc5b 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -364,7 +364,7 @@ private function convertType(Type $type, bool $input = false, string $mutationNa } if ($this->isCollection($type)) { - return $this->paginationEnabled ? $this->getResourcePaginatedCollectionType($resourceClass, $graphqlType, $input) : GraphQLType::listOf($graphqlType); + return $this->paginationEnabled && !$input ? $this->getResourcePaginatedCollectionType($resourceClass, $graphqlType) : GraphQLType::listOf($graphqlType); } return $type->isNullable() || (null !== $mutationName && 'update' === $mutationName) ? $graphqlType : GraphQLType::nonNull($graphqlType); @@ -449,19 +449,16 @@ private function getResourceObjectTypeFields(string $resourceClass, ResourceMeta /** * Gets the type of a paginated collection of the given resource type. * - * @param ObjectType|InputObjectType $resourceType + * @param ObjectType $resourceType * - * @return ObjectType|InputObjectType + * @return ObjectType */ - private function getResourcePaginatedCollectionType(string $resourceClass, GraphQLType $resourceType, bool $input = false): GraphQLType + private function getResourcePaginatedCollectionType(string $resourceClass, GraphQLType $resourceType): GraphQLType { $shortName = $resourceType->name; - if ($input) { - $shortName .= 'Input'; - } - if (isset($this->graphqlTypes[$resourceClass]['connection'][$input])) { - return $this->graphqlTypes[$resourceClass]['connection'][$input]; + if (isset($this->graphqlTypes[$resourceClass]['connection'])) { + return $this->graphqlTypes[$resourceClass]['connection']; } $edgeObjectTypeConfiguration = [ @@ -472,7 +469,7 @@ private function getResourcePaginatedCollectionType(string $resourceClass, Graph 'cursor' => GraphQLType::nonNull(GraphQLType::string()), ], ]; - $edgeObjectType = $input ? new InputObjectType($edgeObjectTypeConfiguration) : new ObjectType($edgeObjectTypeConfiguration); + $edgeObjectType = new ObjectType($edgeObjectTypeConfiguration); $pageInfoObjectTypeConfiguration = [ 'name' => "{$shortName}PageInfo", 'description' => 'Information about the current page.', @@ -481,7 +478,7 @@ private function getResourcePaginatedCollectionType(string $resourceClass, Graph 'hasNextPage' => GraphQLType::nonNull(GraphQLType::boolean()), ], ]; - $pageInfoObjectType = $input ? new InputObjectType($pageInfoObjectTypeConfiguration) : new ObjectType($pageInfoObjectTypeConfiguration); + $pageInfoObjectType = new ObjectType($pageInfoObjectTypeConfiguration); $configuration = [ 'name' => "{$shortName}Connection", @@ -492,7 +489,7 @@ private function getResourcePaginatedCollectionType(string $resourceClass, Graph ], ]; - return $this->graphqlTypes[$resourceClass]['connection'][$input] = $input ? new InputObjectType($configuration) : new ObjectType($configuration); + return $this->graphqlTypes[$resourceClass]['connection'] = new ObjectType($configuration); } private function isCollection(Type $type): bool From e87e099e18d720ff067221ed0d6840506c915fd6 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Mon, 12 Feb 2018 11:19:51 +0100 Subject: [PATCH 3/4] String type (IRI) if depth > 0 (= subresource) for input --- src/GraphQl/Type/SchemaBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 7fa00c2fc5b..fce18d93efa 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -341,7 +341,7 @@ private function convertType(Type $type, bool $input = false, string $mutationNa $graphqlType = $this->graphqlTypes['#iterable']; break; case Type::BUILTIN_TYPE_OBJECT: - if (is_a($type->getClassName(), \DateTimeInterface::class, true)) { + if (($input && $depth > 0) || is_a($type->getClassName(), \DateTimeInterface::class, true)) { $graphqlType = GraphQLType::string(); break; } From b55fd04597cec57ae29fcaa70fe1e1fc361c2ff5 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Mon, 12 Feb 2018 11:20:12 +0100 Subject: [PATCH 4/4] Add a test for a mutation with a subresource --- features/graphql/mutation.feature | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/features/graphql/mutation.feature b/features/graphql/mutation.feature index 5004556ef19..f42c668c709 100644 --- a/features/graphql/mutation.feature +++ b/features/graphql/mutation.feature @@ -54,11 +54,36 @@ Feature: GraphQL mutation support And the JSON node "data.createFoo.bar" should be equal to "new" And the JSON node "data.createFoo.clientMutationId" should be equal to "myId" + Scenario: Create an item with a subresource + Given there are 1 dummy objects with relatedDummy + When I send the following GraphQL request: + """ + mutation { + createDummy(input: {_id: 1, name: "A dummy", foo: [], relatedDummy: "/related_dummies/1", clientMutationId: "myId"}) { + id + name + foo + relatedDummy { + name + } + clientMutationId + } + } + """ + 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.createDummy.id" should be equal to "/dummies/2" + And the JSON node "data.createDummy.name" should be equal to "A dummy" + And the JSON node "data.createDummy.foo" should have 0 elements + And the JSON node "data.createDummy.relatedDummy.name" should be equal to "RelatedDummy #1" + And the JSON node "data.createDummy.clientMutationId" should be equal to "myId" + Scenario: Create an item with an iterable field When I send the following GraphQL request: """ mutation { - createDummy(input: {_id: 1, name: "A dummy", foo: [], jsonData: {bar:{baz:3,qux:[7.6,false,null]}}, clientMutationId: "myId"}) { + createDummy(input: {_id: 2, name: "A dummy", foo: [], jsonData: {bar:{baz:3,qux:[7.6,false,null]}}, clientMutationId: "myId"}) { id name foo @@ -70,7 +95,7 @@ Feature: GraphQL mutation 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.createDummy.id" should be equal to "/dummies/1" + And the JSON node "data.createDummy.id" should be equal to "/dummies/3" And the JSON node "data.createDummy.name" should be equal to "A dummy" And the JSON node "data.createDummy.foo" should have 0 elements And the JSON node "data.createDummy.jsonData.bar.baz" should be equal to the number 3