Skip to content

Commit a840669

Browse files
alanpoulaindunglas
authored andcommitted
Put mutation for an item
1 parent d70925d commit a840669

File tree

6 files changed

+83
-12
lines changed

6 files changed

+83
-12
lines changed

features/graphql/mutation.feature

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ Feature: GraphQL mutation support
3434
And the JSON node "data.__type.fields[0].args[0].name" should be equal to "input"
3535
And the JSON node "data.__type.fields[0].args[0].type.name" should contain "InputDeleteMutation"
3636
And the JSON node "data.__type.fields[0].args[0].type.kind" should be equal to "INPUT_OBJECT"
37-
And print last JSON response
3837

3938
Scenario: Delete an item through a mutation
4039
Given there is 1 dummy objects
@@ -52,7 +51,6 @@ Feature: GraphQL mutation support
5251
And the header "Content-Type" should be equal to "application/json"
5352
And the JSON node "data.deleteDummy.id" should be equal to 1
5453

55-
@dropSchema
5654
Scenario: Delete an item with composite identifiers through a mutation
5755
Given there are Composite identifier objects
5856
When I send the following GraphQL request:
@@ -73,3 +71,47 @@ Feature: GraphQL mutation support
7371
And the header "Content-Type" should be equal to "application/json"
7472
And the JSON node "data.deleteCompositeRelation.compositeItem.id" should be equal to 1
7573
And the JSON node "data.deleteCompositeRelation.compositeLabel.id" should be equal to 1
74+
75+
Scenario: Modify an item through a mutation
76+
Given there is 1 dummy objects
77+
When I send the following GraphQL request:
78+
"""
79+
mutation {
80+
updateDummy(input: {id: 1, description: "Modified description."}) {
81+
id,
82+
name,
83+
description
84+
}
85+
}
86+
"""
87+
Then the response status code should be 200
88+
And the response should be in JSON
89+
And the header "Content-Type" should be equal to "application/json"
90+
And the JSON node "data.updateDummy.id" should be equal to 1
91+
And the JSON node "data.updateDummy.name" should be equal to "Dummy #1"
92+
And the JSON node "data.updateDummy.description" should be equal to "Modified description."
93+
94+
@dropSchema
95+
# @TODO Find a way to manage composite identifiers.
96+
Scenario: Modify an item with composite identifiers through a mutation
97+
Given there are Composite identifier objects
98+
When I send the following GraphQL request:
99+
"""
100+
mutation {
101+
updateCompositeRelation(input: {compositeItem: {id: 1}, compositeLabel: {id: 1}, value: "Modified value."}) {
102+
compositeItem {
103+
id
104+
},
105+
compositeLabel {
106+
id
107+
},
108+
value
109+
}
110+
}
111+
"""
112+
#Then the response status code should be 200
113+
#And the response should be in JSON
114+
#And the header "Content-Type" should be equal to "application/json"
115+
#And the JSON node "data.putCompositeRelation.compositeItem.id" should be equal to 1
116+
#And the JSON node "data.putCompositeRelation.compositeLabel.id" should be equal to 1
117+
#And the JSON node "data.putCompositeRelation.value" should be equal to "Modified value."

src/Bridge/Graphql/Resolver/ItemMutationResolverFactory.php

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
1717
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
1818
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
19+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
1920
use GraphQL\Error\Error;
2021
use GraphQL\Type\Definition\ResolveInfo;
22+
use Symfony\Component\Serializer\Serializer;
2123

2224
/**
2325
* Creates a function resolving a GraphQL mutation of an item.
@@ -30,12 +32,16 @@ final class ItemMutationResolverFactory implements ItemMutationResolverFactoryIn
3032
{
3133
private $identifiersExtractor;
3234
private $itemDataProvider;
35+
private $serializer;
36+
private $resourceMetadataFactory;
3337
private $dataPersister;
3438

35-
public function __construct(IdentifiersExtractorInterface $identifiersExtractor, ItemDataProviderInterface $itemDataProvider, DataPersisterInterface $dataPersister)
39+
public function __construct(IdentifiersExtractorInterface $identifiersExtractor, ItemDataProviderInterface $itemDataProvider, Serializer $serializer, ResourceMetadataFactoryInterface $resourceMetadataFactory, DataPersisterInterface $dataPersister)
3640
{
3741
$this->identifiersExtractor = $identifiersExtractor;
3842
$this->itemDataProvider = $itemDataProvider;
43+
$this->serializer = $serializer;
44+
$this->resourceMetadataFactory = $resourceMetadataFactory;
3945
$this->dataPersister = $dataPersister;
4046
}
4147

@@ -59,17 +65,30 @@ public function createItemMutationResolver(string $resourceClass, string $mutati
5965
} else {
6066
$id = $args['input'][$identifiers[0]];
6167
}
62-
$item = $this->itemDataProvider->getItem($resourceClass, $id);
63-
64-
if (null === $item) {
65-
throw Error::createLocatedError("Item $resourceClass $id not found", $info->fieldNodes, $info->path);
66-
}
6768

6869
if ('delete' === $mutationName) {
70+
$item = $this->itemDataProvider->getItem($resourceClass, $id);
71+
72+
if (null === $item) {
73+
throw Error::createLocatedError("Item $resourceClass $id not found", $info->fieldNodes, $info->path);
74+
}
75+
6976
$this->dataPersister->remove($item);
70-
}
7177

72-
return $args['input'];
78+
return $args['input'];
79+
} elseif ('update' === $mutationName) {
80+
$item = $this->serializer->denormalize($args['input'], $resourceClass, null, ['resource_class' => $resourceClass]);
81+
82+
$this->dataPersister->persist($item);
83+
84+
return $this->serializer->normalize(
85+
$item,
86+
null,
87+
['graphql' => true] + $this->resourceMetadataFactory
88+
->create($resourceClass)
89+
->getGraphqlAttribute($mutationName, 'normalization_context', [], true)
90+
);
91+
}
7392
};
7493
}
7594
}

src/Bridge/Graphql/Type/SchemaBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ private function convertType(Type $type, bool $isInput = false, bool $isMutation
252252
return $this->paginationEnabled ? $this->getResourcePaginatedCollectionType($graphqlType, $isInput) : GraphQLType::listOf($graphqlType);
253253
}
254254

255-
return $type->isNullable() ? $graphqlType : GraphQLType::nonNull($graphqlType);
255+
return $type->isNullable() || ($isMutation && 'update' === $mutationName) ? $graphqlType : GraphQLType::nonNull($graphqlType);
256256
}
257257

258258
/**

src/Bridge/Symfony/Bundle/Resources/config/graphql.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
<service id="api_platform.graphql.item_mutation_resolver_factory" class="ApiPlatform\Core\Bridge\Graphql\Resolver\ItemMutationResolverFactory" public="false">
2929
<argument type="service" id="api_platform.identifiers_extractor.cached" />
3030
<argument type="service" id="api_platform.item_data_provider" />
31+
<argument type="service" id="serializer" />
32+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
3133
<argument type="service" id="api_platform.data_persister" />
3234
</service>
3335

src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function create(string $resourceClass): ResourceMetadata
8484

8585
$graphql = $resourceMetadata->getGraphql();
8686
if (null === $graphql) {
87-
$resourceMetadata = $resourceMetadata->withGraphql(['query' => [], 'delete' => []]);
87+
$resourceMetadata = $resourceMetadata->withGraphql(['query' => [], 'delete' => [], 'update' => []]);
8888
} else {
8989
$resourceMetadata = $this->normalizeGraphQl($resourceMetadata, $graphql);
9090
}

tests/Bridge/Graphql/Resolver/ItemMutationResolverFactoryTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
use ApiPlatform\Core\Bridge\Graphql\Resolver\ItemMutationResolverFactory;
1818
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
1919
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
20+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2021
use GraphQL\Type\Definition\ResolveInfo;
2122
use PHPUnit\Framework\TestCase;
2223
use Prophecy\Argument;
2324
use Prophecy\Prophecy\ObjectProphecy;
25+
use Symfony\Component\Serializer\Serializer;
2426

2527
/**
2628
* @author Alan Poulain <[email protected]>
@@ -107,9 +109,15 @@ private function mockItemMutationResolverFactory($item, array $identifiers, $fla
107109
$itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class);
108110
$itemDataProviderProphecy->getItem('resourceClass', $flatId)->willReturn($item);
109111

112+
$serializerProphecy = $this->prophesize(Serializer::class);
113+
114+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
115+
110116
return new ItemMutationResolverFactory(
111117
$identifiersExtractorProphecy->reveal(),
112118
$itemDataProviderProphecy->reveal(),
119+
$serializerProphecy->reveal(),
120+
$resourceMetadataFactoryProphecy->reveal(),
113121
$dataPersisterProphecy->reveal()
114122
);
115123
}

0 commit comments

Comments
 (0)