Skip to content

Commit dcc9736

Browse files
authored
Merge pull request #1967 from gorghoa/feat/1799-handle-immutable-resource-on-persist
Allow DataPersister to return a new instance instead of mutating
2 parents 677d404 + 5f050fe commit dcc9736

File tree

7 files changed

+98
-8
lines changed

7 files changed

+98
-8
lines changed

src/Bridge/Doctrine/Common/DataPersister.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function supports($data): bool
4848
public function persist($data)
4949
{
5050
if (!$manager = $this->getManager($data)) {
51-
return;
51+
return $data;
5252
}
5353

5454
if (!$manager->contains($data)) {
@@ -57,6 +57,8 @@ public function persist($data)
5757

5858
$manager->flush();
5959
$manager->refresh($data);
60+
61+
return $data;
6062
}
6163

6264
/**

src/DataPersister/ChainDataPersister.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function persist($data)
5151
{
5252
foreach ($this->persisters as $persister) {
5353
if ($persister->supports($data)) {
54-
$persister->persist($data);
54+
return $persister->persist($data) ?? $data;
5555
}
5656
}
5757
}

src/DataPersister/DataPersisterInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public function supports($data): bool;
3333
* Persists the data.
3434
*
3535
* @param mixed $data
36+
*
37+
* @return object|void Void will not be supported in API Platform 3, an object should always be returned
3638
*/
3739
public function persist($data);
3840

src/EventListener/WriteListener.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
5050
case 'PUT':
5151
case 'PATCH':
5252
case 'POST':
53-
$this->dataPersister->persist($controllerResult);
53+
$persistResult = $this->dataPersister->persist($controllerResult);
54+
55+
if (null === $persistResult) {
56+
@trigger_error(sprintf('Returning void from %s::persist() is deprecated since API Platform 2.3 and will not be supported in API Platform 3, an object should always be returned.', DataPersisterInterface::class), E_USER_DEPRECATED);
57+
}
58+
59+
$event->setControllerResult($persistResult ?? $controllerResult);
5460
break;
5561
case 'DELETE':
5662
$this->dataPersister->remove($controllerResult);

src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,13 @@ public function __invoke(string $resourceClass = null, string $rootClass = null,
9393
$context += $resourceMetadata->getGraphqlAttribute($operationName, 'denormalization_context', [], true);
9494
$item = $this->normalizer->denormalize($args['input'], $resourceClass, ItemNormalizer::FORMAT, $context);
9595
$this->validate($item, $info, $resourceMetadata, $operationName);
96-
$this->dataPersister->persist($item);
96+
$persistResult = $this->dataPersister->persist($item);
9797

98-
return $this->normalizer->normalize($item, ItemNormalizer::FORMAT, $normalizationContext) + $data;
98+
if (null === $persistResult) {
99+
@trigger_error(sprintf('Returning void from %s::persist() is deprecated since API Platform 2.3 and will not be supported in API Platform 3, an object should always be returned.', DataPersisterInterface::class), E_USER_DEPRECATED);
100+
}
101+
102+
return $this->normalizer->normalize($persistResult ?? $item, ItemNormalizer::FORMAT, $normalizationContext) + $data;
99103
case 'delete':
100104
if ($item) {
101105
$this->dataPersister->remove($item);

tests/Bridge/Doctrine/Common/DataPersisterTest.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ public function testPersist()
5656
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
5757
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled();
5858

59-
(new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy);
59+
$result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy);
60+
$this->assertSame($dummy, $result);
6061
}
6162

6263
public function testPersistIfEntityAlreadyManaged()
@@ -72,15 +73,19 @@ public function testPersistIfEntityAlreadyManaged()
7273
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
7374
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled();
7475

75-
(new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy);
76+
$result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy);
77+
$this->assertSame($dummy, $result);
7678
}
7779

7880
public function testPersistWithNullManager()
7981
{
82+
$dummy = new Dummy();
83+
8084
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
8185
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled();
8286

83-
(new DataPersister($managerRegistryProphecy->reveal()))->persist(new Dummy());
87+
$result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy);
88+
$this->assertSame($dummy, $result);
8489
}
8590

8691
public function testRemove()

tests/EventListener/WriteListenerTest.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,37 @@ public function testOnKernelViewWithControllerResultAndPersist()
3131
$dummy = new Dummy();
3232
$dummy->setName('Dummyrino');
3333

34+
$dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class);
35+
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();
36+
$dataPersisterProphecy->persist($dummy)->willReturn($dummy)->shouldBeCalled();
37+
38+
$request = new Request();
39+
$request->attributes->set('_api_resource_class', Dummy::class);
40+
41+
$event = new GetResponseForControllerResultEvent(
42+
$this->prophesize(HttpKernelInterface::class)->reveal(),
43+
$request,
44+
HttpKernelInterface::MASTER_REQUEST,
45+
$dummy
46+
);
47+
48+
foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) {
49+
$request->setMethod($httpMethod);
50+
51+
(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
52+
$this->assertSame($dummy, $event->getControllerResult());
53+
}
54+
}
55+
56+
/**
57+
* @group legacy
58+
* @expectedDeprecation Returning void from ApiPlatform\Core\DataPersister\DataPersisterInterface::persist() is deprecated since API Platform 2.3 and will not be supported in API Platform 3, an object should always be returned.
59+
*/
60+
public function testOnKernelViewWithControllerResultAndPersistReturningVoid()
61+
{
62+
$dummy = new Dummy();
63+
$dummy->setName('Dummyrino');
64+
3465
$dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class);
3566
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();
3667
$dataPersisterProphecy->persist($dummy)->shouldBeCalled();
@@ -49,6 +80,46 @@ public function testOnKernelViewWithControllerResultAndPersist()
4980
$request->setMethod($httpMethod);
5081

5182
(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
83+
$this->assertSame($dummy, $event->getControllerResult());
84+
}
85+
}
86+
87+
/**
88+
* @see https://github.com/api-platform/core/issues/1799
89+
*/
90+
public function testOnKernelViewWithControllerResultAndPersistWithImmutableResource()
91+
{
92+
$dummy = new Dummy();
93+
$dummy->setName('Dummyrino');
94+
95+
$dummy2 = new Dummy();
96+
$dummy2->setName('Dummyferoce');
97+
98+
$dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class);
99+
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();
100+
101+
$dataPersisterProphecy
102+
->persist($dummy)
103+
->willReturn($dummy2) // Persist is not mutating $dummy, but return a brand new technically unrelated object instead
104+
->shouldBeCalled()
105+
;
106+
107+
$request = new Request();
108+
$request->attributes->set('_api_resource_class', Dummy::class);
109+
110+
foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) {
111+
$event = new GetResponseForControllerResultEvent(
112+
$this->prophesize(HttpKernelInterface::class)->reveal(),
113+
$request,
114+
HttpKernelInterface::MASTER_REQUEST,
115+
$dummy
116+
);
117+
118+
$request->setMethod($httpMethod);
119+
120+
(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
121+
122+
$this->assertSame($dummy2, $event->getControllerResult());
52123
}
53124
}
54125

0 commit comments

Comments
 (0)