Skip to content

Commit 4fe0821

Browse files
authored
fix(graphql): output creates its own type in TypeBuilder (#4766)
1 parent d876e66 commit 4fe0821

File tree

12 files changed

+223
-15
lines changed

12 files changed

+223
-15
lines changed

features/graphql/mutation.feature

+20
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,26 @@ Feature: GraphQL mutation support
819819
And the JSON node "data.testCustomArgumentsDummyCustomMutation.dummyCustomMutation.result" should be equal to "18"
820820
And the JSON node "data.testCustomArgumentsDummyCustomMutation.clientMutationId" should be equal to "myId"
821821

822+
Scenario: Execute a custom mutation with output
823+
When I send the following GraphQL request:
824+
"""
825+
mutation {
826+
testOutputDummyCustomMutation(input: {id: "/dummy_custom_mutations/1", operandA: 9, clientMutationId: "myId"}) {
827+
dummyCustomMutation {
828+
baz
829+
bat
830+
}
831+
clientMutationId
832+
}
833+
}
834+
"""
835+
Then the response status code should be 200
836+
And the response should be in JSON
837+
And the header "Content-Type" should be equal to "application/json"
838+
And the JSON node "data.testOutputDummyCustomMutation.dummyCustomMutation.baz" should be equal to "98"
839+
And the JSON node "data.testOutputDummyCustomMutation.dummyCustomMutation.bat" should be equal to "9"
840+
And the JSON node "data.testOutputDummyCustomMutation.clientMutationId" should be equal to "myId"
841+
822842
Scenario: Uploading a file with a custom mutation
823843
Given I have the following file for a GraphQL request:
824844
| name | file |

features/graphql/query.feature

+26
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,32 @@ Feature: GraphQL query support
432432
}
433433
"""
434434

435+
Scenario: Custom item query with output
436+
Given there are 2 dummyCustomQuery objects
437+
When I send the following GraphQL request:
438+
"""
439+
{
440+
testItemOutputDummyCustomQuery(id: "/dummy_custom_queries/1",) {
441+
baz
442+
bat
443+
}
444+
}
445+
"""
446+
Then the response status code should be 200
447+
And the response should be in JSON
448+
And the header "Content-Type" should be equal to "application/json"
449+
And the JSON should be equal to:
450+
"""
451+
{
452+
"data": {
453+
"testItemOutputDummyCustomQuery": {
454+
"baz": 46,
455+
"bat": "Success!"
456+
}
457+
}
458+
}
459+
"""
460+
435461
@createSchema
436462
Scenario: Retrieve an item with different serialization groups for item_query and collection_query
437463
Given there are 1 dummy with different GraphQL serialization groups objects

src/Core/GraphQl/Type/TypeBuilder.php

+6-5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadata $
5454
{
5555
$shortName = $resourceMetadata->getShortName();
5656

57+
$ioMetadata = $resourceMetadata->getGraphqlAttribute($subscriptionName ?? $mutationName ?? $queryName, $input ? 'input' : 'output', null, true);
58+
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
59+
$resourceClass = $ioMetadata['class'];
60+
$shortName = $ioMetadata['name'];
61+
}
62+
5763
if (null !== $mutationName) {
5864
$shortName = $mutationName.ucfirst($shortName);
5965
}
@@ -93,11 +99,6 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadata $
9399
return $resourceObjectType;
94100
}
95101

96-
$ioMetadata = $resourceMetadata->getGraphqlAttribute($subscriptionName ?? $mutationName ?? $queryName, $input ? 'input' : 'output', null, true);
97-
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
98-
$resourceClass = $ioMetadata['class'];
99-
}
100-
101102
$wrapData = !$wrapped && (null !== $mutationName || null !== $subscriptionName) && !$input && $depth < 1;
102103

103104
$configuration = [

src/GraphQl/Type/TypeBuilder.php

+6-5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo
5959
$shortName = $operation->getShortName();
6060
$operationName = $operation->getName();
6161

62+
$ioMetadata = $input ? $operation->getInput() : $operation->getOutput();
63+
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
64+
$resourceClass = $ioMetadata['class'];
65+
$shortName = $ioMetadata['name'];
66+
}
67+
6268
if ($operation instanceof Mutation) {
6369
$shortName = $operationName.ucfirst($shortName);
6470
}
@@ -102,11 +108,6 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo
102108
return $resourceObjectType;
103109
}
104110

105-
$ioMetadata = $input ? $operation->getInput() : $operation->getOutput();
106-
if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
107-
$resourceClass = $ioMetadata['class'];
108-
}
109-
110111
$wrapData = !$wrapped && ($operation instanceof Mutation || $operation instanceof Subscription) && !$input && $depth < 1;
111112

112113
$configuration = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer;
15+
16+
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Document\DummyCustomMutation as DummyCustomMutationDocument;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCustomMutation;
20+
21+
final class DummyCustomMutationDtoDataTransformer implements DataTransformerInterface
22+
{
23+
/**
24+
* {@inheritdoc}
25+
*
26+
* @return object
27+
*/
28+
public function transform($object, string $to, array $context = [])
29+
{
30+
if ($object instanceof \Traversable) {
31+
foreach ($object as &$value) {
32+
$value = $this->doTransformation($value);
33+
}
34+
35+
return $object;
36+
}
37+
38+
return $this->doTransformation($object);
39+
}
40+
41+
private function doTransformation($object): OutputDto
42+
{
43+
$output = new OutputDto();
44+
$output->baz = 98;
45+
$output->bat = $object->getOperandA();
46+
47+
return $output;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function supportsTransformation($object, string $to, array $context = []): bool
54+
{
55+
if ($object instanceof \IteratorAggregate) {
56+
$iterator = $object->getIterator();
57+
if ($iterator instanceof \Iterator) {
58+
$object = $iterator->current();
59+
}
60+
}
61+
62+
return ($object instanceof DummyCustomMutation || $object instanceof DummyCustomMutationDocument) && OutputDto::class === $to;
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer;
15+
16+
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Document\DummyCustomQuery as DummyCustomQueryDocument;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCustomQuery;
20+
21+
final class DummyCustomQueryDtoDataTransformer implements DataTransformerInterface
22+
{
23+
/**
24+
* {@inheritdoc}
25+
*
26+
* @return object
27+
*/
28+
public function transform($object, string $to, array $context = [])
29+
{
30+
if ($object instanceof \Traversable) {
31+
foreach ($object as &$value) {
32+
$value = $this->doTransformation($value);
33+
}
34+
35+
return $object;
36+
}
37+
38+
return $this->doTransformation($object);
39+
}
40+
41+
private function doTransformation($object): OutputDto
42+
{
43+
$output = new OutputDto();
44+
$output->baz = 46;
45+
$output->bat = $object->message;
46+
47+
return $output;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function supportsTransformation($object, string $to, array $context = []): bool
54+
{
55+
if ($object instanceof \IteratorAggregate) {
56+
$iterator = $object->getIterator();
57+
if ($iterator instanceof \Iterator) {
58+
$object = $iterator->current();
59+
}
60+
}
61+
62+
return ($object instanceof DummyCustomQuery || $object instanceof DummyCustomQueryDocument) && OutputDto::class === $to;
63+
}
64+
}

tests/Fixtures/TestBundle/Document/DummyCustomMutation.php

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
1718
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
1819
use Symfony\Component\Serializer\Annotation\Groups;
1920

@@ -50,6 +51,10 @@
5051
* "testCustomArguments"={
5152
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
5253
* "args"={"operandC"={"type"="Int!"}}
54+
* },
55+
* "testOutput"={
56+
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
57+
* "output"=OutputDto::class
5358
* }
5459
* })
5560
*

tests/Fixtures/TestBundle/Document/DummyCustomQuery.php

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
1718
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
1819

1920
/**
@@ -47,6 +48,10 @@
4748
* "customArgumentCustomType"={"type"="DateTime!"}
4849
* }
4950
* },
51+
* "testItemOutput"={
52+
* "item_query"="app.graphql.query_resolver.dummy_custom_item",
53+
* "output"=OutputDto::class
54+
* },
5055
* "testCollection"={
5156
* "collection_query"="app.graphql.query_resolver.dummy_custom_collection"
5257
* },

tests/Fixtures/TestBundle/Entity/DummyCustomMutation.php

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
1718
use Doctrine\ORM\Mapping as ORM;
1819
use Symfony\Component\Serializer\Annotation\Groups;
1920

@@ -50,6 +51,10 @@
5051
* "testCustomArguments"={
5152
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
5253
* "args"={"operandC"={"type"="Int!"}}
54+
* },
55+
* "testOutput"={
56+
* "mutation"="app.graphql.mutation_resolver.dummy_custom",
57+
* "output"=OutputDto::class
5358
* }
5459
* })
5560
*

tests/Fixtures/TestBundle/Entity/DummyCustomQuery.php

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto;
1718
use Doctrine\ORM\Mapping as ORM;
1819

1920
/**
@@ -47,6 +48,10 @@
4748
* "customArgumentCustomType"={"type"="DateTime!"}
4849
* }
4950
* },
51+
* "testItemOutput"={
52+
* "item_query"="app.graphql.query_resolver.dummy_custom_item",
53+
* "output"=OutputDto::class
54+
* },
5055
* "testCollection"={
5156
* "collection_query"="app.graphql.query_resolver.dummy_custom_collection"
5257
* },

tests/Fixtures/app/config/config_common.yml

+12
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,18 @@ services:
306306
tags:
307307
- { name: 'api_platform.data_transformer' }
308308

309+
app.data_transformer.dummy_custom_query_dto:
310+
class: 'ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer\DummyCustomQueryDtoDataTransformer'
311+
public: false
312+
tags:
313+
- { name: 'api_platform.data_transformer' }
314+
315+
app.data_transformer.dummy_custom_mutation_dto:
316+
class: 'ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer\DummyCustomMutationDtoDataTransformer'
317+
public: false
318+
tags:
319+
- { name: 'api_platform.data_transformer' }
320+
309321
app.data_transformer.custom_output_dto_fallback_same_class:
310322
class: 'ApiPlatform\Tests\Fixtures\TestBundle\DataTransformer\OutputDtoSameClassTransformer'
311323
public: false

tests/GraphQl/Type/TypeBuilderTest.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,23 @@ public function testGetResourceObjectType(): void
108108
public function testGetResourceObjectTypeOutputClass(): void
109109
{
110110
$resourceMetadata = new ResourceMetadataCollection('resourceClass', []);
111-
$this->typesContainerProphecy->has('shortName')->shouldBeCalled()->willReturn(false);
112-
$this->typesContainerProphecy->set('shortName', Argument::type(ObjectType::class))->shouldBeCalled();
111+
$this->typesContainerProphecy->has('outputName')->shouldBeCalled()->willReturn(false);
112+
$this->typesContainerProphecy->set('outputName', Argument::type(ObjectType::class))->shouldBeCalled();
113113
$this->typesContainerProphecy->has('Node')->shouldBeCalled()->willReturn(false);
114114
$this->typesContainerProphecy->set('Node', Argument::type(InterfaceType::class))->shouldBeCalled();
115115

116116
/** @var Operation $operation */
117-
$operation = (new Query())->withShortName('shortName')->withDescription('description')->withOutput(['class' => 'outputClass']);
117+
$operation = (new Query())->withShortName('shortName')->withDescription('description')->withOutput(['class' => 'outputClass', 'name' => 'outputName']);
118118
/** @var ObjectType $resourceObjectType */
119119
$resourceObjectType = $this->typeBuilder->getResourceObjectType('resourceClass', $resourceMetadata, $operation, false);
120-
$this->assertSame('shortName', $resourceObjectType->name);
120+
$this->assertSame('outputName', $resourceObjectType->name);
121121
$this->assertSame('description', $resourceObjectType->description);
122122
$this->assertSame($this->defaultFieldResolver, $resourceObjectType->resolveFieldFn);
123123
$this->assertArrayHasKey('interfaces', $resourceObjectType->config);
124124
$this->assertArrayHasKey('fields', $resourceObjectType->config);
125125

126126
$fieldsBuilderProphecy = $this->prophesize(FieldsBuilderInterface::class);
127-
$fieldsBuilderProphecy->getResourceObjectTypeFields('outputClass', $operation, false, 0, ['class' => 'outputClass'])->shouldBeCalled();
127+
$fieldsBuilderProphecy->getResourceObjectTypeFields('outputClass', $operation, false, 0, ['class' => 'outputClass', 'name' => 'outputName'])->shouldBeCalled();
128128
$this->fieldsBuilderLocatorProphecy->get('api_platform.graphql.fields_builder')->shouldBeCalled()->willReturn($fieldsBuilderProphecy->reveal());
129129
$resourceObjectType->config['fields']();
130130
}

0 commit comments

Comments
 (0)