Skip to content

Commit 0fdc39b

Browse files
authored
Merge pull request #624 from dunglas/item_normalizer
Add a generic ItemNormalizer (relation support for raw JSON, XML, CSV, YAML)
2 parents 2955a93 + 9349a8c commit 0fdc39b

File tree

11 files changed

+216
-28
lines changed

11 files changed

+216
-28
lines changed

features/content_negotiation.feature

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Feature: Content Negotiation support
1717
And the response should be equal to
1818
"""
1919
<?xml version="1.0"?>
20-
<response><id>1</id><name>XML!</name><alias/><description/><dummyDate/><dummyPrice/><jsonData/><relatedDummy/><dummyBoolean/><dummy/><relatedDummies/><nameConverted/></response>
20+
<response><id>/dummies/1</id><description/><dummy/><dummyBoolean/><dummyDate/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><name_converted/><name>XML!</name><alias/></response>
2121
"""
2222

2323
Scenario: Retrieve a collection in XML
@@ -28,7 +28,7 @@ Feature: Content Negotiation support
2828
And the response should be equal to
2929
"""
3030
<?xml version="1.0"?>
31-
<response><item key="0"><id>1</id><name>XML!</name><alias/><description/><dummyDate/><dummyPrice/><jsonData/><relatedDummy/><dummyBoolean/><dummy/><relatedDummies/><nameConverted/></item></response>
31+
<response><item key="0"><id>/dummies/1</id><description/><dummy/><dummyBoolean/><dummyDate/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><name_converted/><name>XML!</name><alias/></item></response>
3232
"""
3333

3434
Scenario: Retrieve a collection in XML using the .xml URL
@@ -38,7 +38,7 @@ Feature: Content Negotiation support
3838
And the response should be equal to
3939
"""
4040
<?xml version="1.0"?>
41-
<response><item key="0"><id>1</id><name>XML!</name><alias/><description/><dummyDate/><dummyPrice/><jsonData/><relatedDummy/><dummyBoolean/><dummy/><relatedDummies/><nameConverted/></item></response>
41+
<response><item key="0"><id>/dummies/1</id><description/><dummy/><dummyBoolean/><dummyDate/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><name_converted/><name>XML!</name><alias/></item></response>
4242
"""
4343

4444
Scenario: Retrieve a collection in JSON
@@ -51,18 +51,18 @@ Feature: Content Negotiation support
5151
"""
5252
[
5353
{
54-
"id": 1,
55-
"name": "XML!",
56-
"alias": null,
54+
"id": "/dummies/1",
5755
"description": null,
56+
"dummy": null,
57+
"dummyBoolean": null,
5858
"dummyDate": null,
5959
"dummyPrice": null,
60-
"jsonData": [],
6160
"relatedDummy": null,
62-
"dummyBoolean": null,
63-
"dummy": null,
6461
"relatedDummies": [],
65-
"nameConverted": null
62+
"jsonData": [],
63+
"name_converted": null,
64+
"name": "XML!",
65+
"alias": null
6666
}
6767
]
6868
"""
@@ -79,7 +79,7 @@ Feature: Content Negotiation support
7979
And the response should be equal to
8080
"""
8181
<?xml version="1.0"?>
82-
<response><id>2</id><name>Sent in JSON</name><alias/><description/><dummyDate/><dummyPrice/><jsonData/><relatedDummy/><dummyBoolean/><dummy/><relatedDummies/><nameConverted/></response>
82+
<response><id>/dummies/2</id><description/><dummy/><dummyBoolean/><dummyDate/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><name_converted/><name>Sent in JSON</name><alias/></response>
8383
"""
8484

8585
@dropSchema

features/hal.feature

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@ Feature: HAL support
55

66
@createSchema
77
Scenario: Create a third level
8-
When I send a "POST" request to "/third_levels" with body:
8+
When I add "Content-Type" header equal to "application/json"
9+
And I send a "POST" request to "/third_levels" with body:
910
"""
1011
{"level": 3}
1112
"""
1213
Then the response status code should be 201
1314

1415
Scenario: Create a related dummy
15-
When I send a "POST" request to "/related_dummies" with body:
16+
When I add "Content-Type" header equal to "application/json"
17+
And I send a "POST" request to "/related_dummies" with body:
1618
"""
1719
{"thirdLevel": "/third_levels/1"}
1820
"""
1921
Then the response status code should be 201
2022

2123
Scenario: Create a dummy with relations
22-
When I send a "POST" request to "/dummies" with body:
24+
When I add "Content-Type" header equal to "application/json"
25+
And I send a "POST" request to "/dummies" with body:
2326
"""
2427
{
2528
"name": "Dummy with relations",
@@ -68,12 +71,11 @@ Feature: HAL support
6871

6972
Scenario: Update a resource
7073
When I add "Accept" header equal to "application/hal+json"
74+
And I add "Content-Type" header equal to "application/json"
7175
And I send a "PUT" request to "/dummies/1" with body:
72-
"""
73-
{
74-
"name": "A nice dummy"
75-
}
76-
"""
76+
"""
77+
{"name": "A nice dummy"}
78+
"""
7779
Then the response status code should be 200
7880
And the response should be in JSON
7981
And the header "Content-Type" should be equal to "application/hal+json"
@@ -106,7 +108,8 @@ Feature: HAL support
106108
"""
107109

108110
Scenario: Embed a relation in a parent object
109-
When I send a "POST" request to "/relation_embedders" with body:
111+
When I add "Content-Type" header equal to "application/json"
112+
And I send a "POST" request to "/relation_embedders" with body:
110113
"""
111114
{
112115
"related": "/related_dummies/1"

features/overridden_operation.feature

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ Feature: Create-Retrieve-Update-Delete with a Overridden Operation context
5252
And the header "Content-Type" should be equal to "application/xml"
5353
And the response should be equal to
5454
"""
55-
<?xml version="1.0"?>
56-
<response><name>My Overridden Operation Dummy</name><alias/><description>Gerard</description></response>
55+
<?xml version="1.0"?>
56+
<response><id>/overridden_operation_dummies/1</id><name>My Overridden Operation Dummy</name><alias/><description>Gerard</description></response>
5757
"""
5858

5959
Scenario: Get a not found exception
@@ -69,11 +69,11 @@ Feature: Create-Retrieve-Update-Delete with a Overridden Operation context
6969
"""
7070
{
7171
"@context": "/contexts/OverriddenOperationDummy",
72-
"@id": "\/overridden_operation_dummies",
72+
"@id": "/overridden_operation_dummies",
7373
"@type": "hydra:Collection",
7474
"hydra:member": [
7575
{
76-
"@id": "\/overridden_operation_dummies\/1",
76+
"@id": "/overridden_operation_dummies/1",
7777
"@type": "OverriddenOperationDummy",
7878
"name": "My Overridden Operation Dummy",
7979
"alias": null,

features/relation.feature

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ Feature: Relations support
55

66
@createSchema
77
Scenario: Create a third level
8-
When I send a "POST" request to "/third_levels" with body:
8+
When I add "Content-Type" header equal to "application/ld+json"
9+
And I send a "POST" request to "/third_levels" with body:
910
"""
1011
{"level": 3}
1112
"""

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@
4646
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
4747
</service>
4848

49+
<service id="api_platform.serializer.normalizer.item" class="ApiPlatform\Core\Serializer\ItemNormalizer" public="false">
50+
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
51+
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
52+
<argument type="service" id="api_platform.iri_converter" />
53+
<argument type="service" id="api_platform.resource_class_resolver" />
54+
<argument type="service" id="api_platform.property_accessor" />
55+
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
56+
57+
<tag name="serializer.normalizer" />
58+
</service>
59+
4960
<!-- Resource path naming strategies -->
5061

5162
<service id="api_platform.naming.resource_path_naming_strategy.underscore" class="ApiPlatform\Core\Naming\UnderscoreResourcePathNamingStrategy" public="false" />

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<argument type="service" id="api_platform.property_accessor" />
2222
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
2323

24-
<tag name="serializer.normalizer" />
24+
<tag name="serializer.normalizer" priority="8" />
2525
</service>
2626

2727
<service id="api_platform.hal.normalizer.collection" class="ApiPlatform\Core\Hal\Serializer\CollectionNormalizer" public="false">

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<argument type="service" id="api_platform.property_accessor" />
2626
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
2727

28-
<tag name="serializer.normalizer" />
28+
<tag name="serializer.normalizer" priority="8" />
2929
</service>
3030

3131
<service id="api_platform.jsonld.encoder" class="ApiPlatform\Core\Serializer\JsonEncoder" public="false">

src/Serializer/ItemNormalizer.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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\Serializer;
13+
14+
/**
15+
* Generic item normalizer.
16+
*
17+
* @author Kévin Dunglas <[email protected]>
18+
*/
19+
class ItemNormalizer extends AbstractItemNormalizer
20+
{
21+
public function normalize($object, $format = null, array $context = [])
22+
{
23+
$rawData = parent::normalize($object, $format, $context);
24+
if (!is_array($rawData)) {
25+
return $rawData;
26+
}
27+
28+
if (!isset($data['id'])) {
29+
$data['id'] = $this->iriConverter->getIriFromItem($object);
30+
}
31+
32+
return array_merge($data, $rawData);
33+
}
34+
35+
public function denormalize($data, $class, $format = null, array $context = [])
36+
{
37+
// Avoid issues with proxies if we populated the object
38+
if (isset($data['id']) && !isset($context['object_to_populate'])) {
39+
$context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], true);
40+
}
41+
42+
return parent::denormalize($data, $class, $format, $context); // TODO: Change the autogenerated stub
43+
}
44+
}

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ private function getContainerBuilderProphecy()
259259
'api_platform.listener.view.serialize',
260260
'api_platform.listener.view.validate',
261261
'api_platform.listener.view.respond',
262+
'api_platform.serializer.normalizer.item',
262263
'api_platform.serializer.context_builder',
263264
'api_platform.doctrine.metadata_factory',
264265
'api_platform.doctrine.orm.collection_data_provider',

tests/Hal/Serializer/ItemNormalizerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace ApiPlatform\Core\tests\Hal;
12+
namespace ApiPlatform\Core\Tests\Hal;
1313

1414
use ApiPlatform\Core\Api\IriConverterInterface;
1515
use ApiPlatform\Core\Api\ResourceClassResolverInterface;

0 commit comments

Comments
 (0)