Skip to content

Commit 01303fa

Browse files
authored
Merge pull request #877 from Simperfit/fix/857-embedded
fix #857 - fix on put when both @id and id are present
2 parents f9707ec + 137a36e commit 01303fa

File tree

6 files changed

+217
-6
lines changed

6 files changed

+217
-6
lines changed

features/main/custom_normalized.feature

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,67 @@ Feature: Using custom normalized entity
2222
"@context": "/contexts/CustomNormalizedDummy",
2323
"@id": "/custom_normalized_dummies/1",
2424
"@type": "CustomNormalizedDummy",
25+
"id": 1,
2526
"name": "My Dummy",
2627
"alias": "My alias"
2728
}
2829
"""
2930

30-
Scenario: Get a resource
31+
Scenario: Create a resource with a custom normalized dummy
32+
When I add "Content-Type" header equal to "application/json"
33+
When I add "Accept" header equal to "application/json"
34+
And I send a "POST" request to "/related_normalized_dummies" with body:
35+
"""
36+
{
37+
"name": "My Dummy"
38+
}
39+
"""
40+
Then the response status code should be 201
41+
And the response should be in JSON
42+
And the header "Content-Type" should be equal to "application/json; charset=utf-8"
43+
And the JSON should be equal to:
44+
"""
45+
{
46+
"id": 1,
47+
"name": "My Dummy",
48+
"customNormalizedDummy": []
49+
}
50+
"""
51+
52+
Scenario: Create a resource with a custom normalized dummy and an id
53+
When I add "Content-Type" header equal to "application/json"
54+
When I add "Accept" header equal to "application/json"
55+
And I send a "PUT" request to "/related_normalized_dummies/1" with body:
56+
"""
57+
{
58+
"name": "My Dummy",
59+
"customNormalizedDummy":[{
60+
"@context": "/contexts/CustomNormalizedDummy",
61+
"@id": "/custom_normalized_dummies/1",
62+
"@type": "CustomNormalizedDummy",
63+
"id": 1,
64+
"name": "My Dummy"
65+
}]
66+
}
67+
"""
68+
Then the response status code should be 200
69+
And the response should be in JSON
70+
And the header "Content-Type" should be equal to "application/json; charset=utf-8"
71+
And the JSON should be equal to:
72+
"""
73+
{
74+
"id": 1,
75+
"name": "My Dummy",
76+
"customNormalizedDummy":[{
77+
"id": 1,
78+
"name": "My Dummy",
79+
"alias": "My alias"
80+
}]
81+
}
82+
"""
83+
84+
85+
Scenario: Get a custom normalized dummy resource
3186
When I send a "GET" request to "/custom_normalized_dummies/1"
3287
Then the response status code should be 200
3388
And the response should be in JSON
@@ -38,6 +93,7 @@ Feature: Using custom normalized entity
3893
"@context": "/contexts/CustomNormalizedDummy",
3994
"@id": "/custom_normalized_dummies/1",
4095
"@type": "CustomNormalizedDummy",
96+
"id": 1,
4197
"name": "My Dummy",
4298
"alias": "My alias"
4399
}
@@ -58,6 +114,7 @@ Feature: Using custom normalized entity
58114
{
59115
"@id": "/custom_normalized_dummies/1",
60116
"@type": "CustomNormalizedDummy",
117+
"id": 1,
61118
"name": "My Dummy",
62119
"alias": "My alias"
63120
}
@@ -83,6 +140,7 @@ Feature: Using custom normalized entity
83140
"@context": "/contexts/CustomNormalizedDummy",
84141
"@id": "/custom_normalized_dummies/1",
85142
"@type": "CustomNormalizedDummy",
143+
"id": 1,
86144
"name": "My Dummy modified",
87145
"alias": "My alias"
88146
}

src/Serializer/ItemNormalizer.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,30 @@ public function denormalize($data, $class, $format = null, array $context = [])
3333
throw new InvalidArgumentException('Update is not allowed for this operation.');
3434
}
3535

36-
$context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], $context + ['fetch_data' => false]);
36+
$this->updateObjectToPopulate($data, $context);
3737
}
3838

3939
return parent::denormalize($data, $class, $format, $context);
4040
}
41+
42+
private function updateObjectToPopulate(array $data, array &$context)
43+
{
44+
try {
45+
$context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], $context + ['fetch_data' => false]);
46+
} catch (InvalidArgumentException $e) {
47+
$identifier = null;
48+
foreach ($this->propertyNameCollectionFactory->create($context['resource_class'], $context) as $propertyName) {
49+
if (true === $this->propertyMetadataFactory->create($context['resource_class'], $propertyName)->isIdentifier()) {
50+
$identifier = $propertyName;
51+
break;
52+
}
53+
}
54+
55+
if (null === $identifier) {
56+
throw $e;
57+
}
58+
59+
$context['object_to_populate'] = $this->iriConverter->getItemFromIri(sprintf('%s/%s', $this->iriConverter->getIriFromResourceClass($context['resource_class']), $data[$identifier]), $context + ['fetch_data' => false]);
60+
}
61+
}
4162
}

tests/Fixtures/TestBundle/Entity/CustomNormalizedDummy.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class CustomNormalizedDummy
3636
* @ORM\Column(type="integer")
3737
* @ORM\Id
3838
* @ORM\GeneratedValue(strategy="AUTO")
39+
* @Groups({"input", "output"})
3940
*/
4041
private $id;
4142

tests/Fixtures/TestBundle/Entity/DummyFriend.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class DummyFriend
4949
/**
5050
* Get id.
5151
*
52-
* @return id
52+
* @return int
5353
*/
5454
public function getId()
5555
{
@@ -69,7 +69,7 @@ public function setId($id)
6969
/**
7070
* Get name.
7171
*
72-
* @return name
72+
* @return string
7373
*/
7474
public function getName()
7575
{
@@ -79,7 +79,7 @@ public function getName()
7979
/**
8080
* Set name.
8181
*
82-
* @param name the value to set
82+
* @param string the value to set
8383
*/
8484
public function setName($name)
8585
{
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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+
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
13+
14+
use ApiPlatform\Core\Annotation\ApiProperty;
15+
use ApiPlatform\Core\Annotation\ApiResource;
16+
use Doctrine\Common\Collections\ArrayCollection;
17+
use Doctrine\ORM\Mapping as ORM;
18+
use Symfony\Component\Serializer\Annotation\Groups;
19+
use Symfony\Component\Validator\Constraints as Assert;
20+
21+
/**
22+
* Related to Normalized Dummy.
23+
*
24+
* @author Amrouche Hamza <[email protected]>
25+
*
26+
* @ApiResource(attributes={
27+
* "normalization_context"={"groups"={"related_output", "output"}},
28+
* "denormalization_context"={"groups"={"related_input", "input"}}
29+
* })
30+
* @ORM\Entity
31+
*/
32+
class RelatedNormalizedDummy
33+
{
34+
/**
35+
* @var int The id
36+
*
37+
* @ORM\Column(type="integer")
38+
* @ORM\Id
39+
* @ORM\GeneratedValue(strategy="AUTO")
40+
* @Groups({"related_output", "related_input"})
41+
*/
42+
private $id;
43+
44+
/**
45+
* @var string The dummy name
46+
*
47+
* @ORM\Column
48+
* @Assert\NotBlank
49+
* @ApiProperty(iri="http://schema.org/name")
50+
* @Groups({"related_output", "related_input"})
51+
*/
52+
private $name;
53+
54+
/**
55+
* @var ArrayCollection Several Normalized dummies
56+
*
57+
* @ORM\ManyToMany(targetEntity="CustomNormalizedDummy")
58+
* @Groups({"related_output", "related_input"})
59+
*/
60+
public $customNormalizedDummy;
61+
62+
public function __construct()
63+
{
64+
$this->customNormalizedDummy = new ArrayCollection();
65+
}
66+
67+
public function getId(): int
68+
{
69+
return $this->id;
70+
}
71+
72+
/**
73+
* @param string $name
74+
*/
75+
public function setName($name)
76+
{
77+
$this->name = $name;
78+
}
79+
80+
public function getName(): string
81+
{
82+
return $this->name;
83+
}
84+
85+
/**
86+
* @return ArrayCollection
87+
*/
88+
public function getCustomNormalizedDummy()
89+
{
90+
return $this->customNormalizedDummy;
91+
}
92+
93+
/**
94+
* @param ArrayCollection $customNormalizedDummy
95+
*/
96+
public function setCustomNormalizedDummy($customNormalizedDummy)
97+
{
98+
$this->customNormalizedDummy = $customNormalizedDummy;
99+
}
100+
}

tests/Serializer/ItemNormalizerTest.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,37 @@ public function testDenormalize()
125125
$this->assertInstanceOf(Dummy::class, $normalizer->denormalize(['name' => 'hello'], Dummy::class, null, $context));
126126
}
127127

128+
public function testDenormalizeWithIri()
129+
{
130+
$context = ['resource_class' => Dummy::class, 'api_allow_update' => true];
131+
132+
$propertyNameCollection = new PropertyNameCollection(['name']);
133+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
134+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled();
135+
136+
$propertyMetadataFactory = new PropertyMetadata(null, null, true);
137+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
138+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadataFactory)->shouldBeCalled();
139+
140+
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
141+
$iriConverterProphecy->getItemFromIri('/dummies/12', ['resource_class' => Dummy::class, 'api_allow_update' => true, 'fetch_data' => false])->shouldBeCalled();
142+
143+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
144+
145+
$serializerProphecy = $this->prophesize(SerializerInterface::class);
146+
$serializerProphecy->willImplement(DenormalizerInterface::class);
147+
148+
$normalizer = new ItemNormalizer(
149+
$propertyNameCollectionFactoryProphecy->reveal(),
150+
$propertyMetadataFactoryProphecy->reveal(),
151+
$iriConverterProphecy->reveal(),
152+
$resourceClassResolverProphecy->reveal()
153+
);
154+
$normalizer->setSerializer($serializerProphecy->reveal());
155+
156+
$this->assertInstanceOf(Dummy::class, $normalizer->denormalize(['id' => '/dummies/12', 'name' => 'hello'], Dummy::class, null, $context));
157+
}
158+
128159
/**
129160
* @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
130161
* @expectedExceptionMessage Update is not allowed for this operation.
@@ -151,6 +182,6 @@ public function testDenormalizeWithIdAndUpdateNotAllowed()
151182
$resourceClassResolverProphecy->reveal()
152183
);
153184
$normalizer->setSerializer($serializerProphecy->reveal());
154-
$normalizer->denormalize(['id' => '/dummies/12', 'name' => 'hello'], Dummy::class, null, $context);
185+
$normalizer->denormalize(['id' => '12', 'name' => 'hello'], Dummy::class, null, $context);
155186
}
156187
}

0 commit comments

Comments
 (0)