diff --git a/features/content_negotiation.feature b/features/content_negotiation.feature
index 47e98e93983..770fe75dfe6 100644
--- a/features/content_negotiation.feature
+++ b/features/content_negotiation.feature
@@ -17,7 +17,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
- 1XML!
+ /dummies/1XML!
"""
Scenario: Retrieve a collection in XML
@@ -28,7 +28,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
- - 1XML!
+ - /dummies/1XML!
"""
Scenario: Retrieve a collection in XML using the .xml URL
@@ -38,7 +38,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
- - 1XML!
+ - /dummies/1XML!
"""
Scenario: Retrieve a collection in JSON
@@ -51,18 +51,18 @@ Feature: Content Negotiation support
"""
[
{
- "id": 1,
- "name": "XML!",
- "alias": null,
+ "id": "/dummies/1",
"description": null,
+ "dummy": null,
+ "dummyBoolean": null,
"dummyDate": null,
"dummyPrice": null,
- "jsonData": [],
"relatedDummy": null,
- "dummyBoolean": null,
- "dummy": null,
"relatedDummies": [],
- "nameConverted": null
+ "jsonData": [],
+ "name_converted": null,
+ "name": "XML!",
+ "alias": null
}
]
"""
@@ -79,7 +79,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
- 2Sent in JSON
+ /dummies/2Sent in JSON
"""
@dropSchema
diff --git a/features/hal.feature b/features/hal.feature
index 86d348ec931..8c65b6ebd0b 100644
--- a/features/hal.feature
+++ b/features/hal.feature
@@ -5,21 +5,24 @@ Feature: HAL support
@createSchema
Scenario: Create a third level
- When I send a "POST" request to "/third_levels" with body:
+ When I add "Content-Type" header equal to "application/json"
+ And I send a "POST" request to "/third_levels" with body:
"""
{"level": 3}
"""
Then the response status code should be 201
Scenario: Create a related dummy
- When I send a "POST" request to "/related_dummies" with body:
+ When I add "Content-Type" header equal to "application/json"
+ And I send a "POST" request to "/related_dummies" with body:
"""
{"thirdLevel": "/third_levels/1"}
"""
Then the response status code should be 201
Scenario: Create a dummy with relations
- When I send a "POST" request to "/dummies" with body:
+ When I add "Content-Type" header equal to "application/json"
+ And I send a "POST" request to "/dummies" with body:
"""
{
"name": "Dummy with relations",
@@ -68,12 +71,11 @@ Feature: HAL support
Scenario: Update a resource
When I add "Accept" header equal to "application/hal+json"
+ And I add "Content-Type" header equal to "application/json"
And I send a "PUT" request to "/dummies/1" with body:
- """
- {
- "name": "A nice dummy"
- }
- """
+ """
+ {"name": "A nice dummy"}
+ """
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/hal+json"
@@ -106,7 +108,8 @@ Feature: HAL support
"""
Scenario: Embed a relation in a parent object
- When I send a "POST" request to "/relation_embedders" with body:
+ When I add "Content-Type" header equal to "application/json"
+ And I send a "POST" request to "/relation_embedders" with body:
"""
{
"related": "/related_dummies/1"
diff --git a/features/overridden_operation.feature b/features/overridden_operation.feature
index a19ededcdf1..46c229d8081 100644
--- a/features/overridden_operation.feature
+++ b/features/overridden_operation.feature
@@ -52,8 +52,8 @@ Feature: Create-Retrieve-Update-Delete with a Overridden Operation context
And the header "Content-Type" should be equal to "application/xml"
And the response should be equal to
"""
-
-My Overridden Operation DummyGerard
+
+ /overridden_operation_dummies/1My Overridden Operation DummyGerard
"""
Scenario: Get a not found exception
@@ -69,11 +69,11 @@ Feature: Create-Retrieve-Update-Delete with a Overridden Operation context
"""
{
"@context": "/contexts/OverriddenOperationDummy",
- "@id": "\/overridden_operation_dummies",
+ "@id": "/overridden_operation_dummies",
"@type": "hydra:Collection",
"hydra:member": [
{
- "@id": "\/overridden_operation_dummies\/1",
+ "@id": "/overridden_operation_dummies/1",
"@type": "OverriddenOperationDummy",
"name": "My Overridden Operation Dummy",
"alias": null,
diff --git a/features/relation.feature b/features/relation.feature
index 38b70ef9962..b76b8ee1949 100644
--- a/features/relation.feature
+++ b/features/relation.feature
@@ -5,7 +5,8 @@ Feature: Relations support
@createSchema
Scenario: Create a third level
- When I send a "POST" request to "/third_levels" with body:
+ When I add "Content-Type" header equal to "application/ld+json"
+ And I send a "POST" request to "/third_levels" with body:
"""
{"level": 3}
"""
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml
index 059e2c8f7de..40dbb25b446 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml
@@ -46,6 +46,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml b/src/Bridge/Symfony/Bundle/Resources/config/hal.xml
index f5fcd603d7a..8c1a411f333 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/hal.xml
@@ -21,7 +21,7 @@
-
+
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml b/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml
index 2fc020aa646..acb9472edec 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml
@@ -25,7 +25,7 @@
-
+
diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php
new file mode 100644
index 00000000000..7cd7ed729d1
--- /dev/null
+++ b/src/Serializer/ItemNormalizer.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Serializer;
+
+/**
+ * Generic item normalizer.
+ *
+ * @author Kévin Dunglas
+ */
+class ItemNormalizer extends AbstractItemNormalizer
+{
+ public function normalize($object, $format = null, array $context = [])
+ {
+ $rawData = parent::normalize($object, $format, $context);
+ if (!is_array($rawData)) {
+ return $rawData;
+ }
+
+ if (!isset($data['id'])) {
+ $data['id'] = $this->iriConverter->getIriFromItem($object);
+ }
+
+ return array_merge($data, $rawData);
+ }
+
+ public function denormalize($data, $class, $format = null, array $context = [])
+ {
+ // Avoid issues with proxies if we populated the object
+ if (isset($data['id']) && !isset($context['object_to_populate'])) {
+ $context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], true);
+ }
+
+ return parent::denormalize($data, $class, $format, $context); // TODO: Change the autogenerated stub
+ }
+}
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index 58a70ef0bbe..9b9d6e9dd77 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -259,6 +259,7 @@ private function getContainerBuilderProphecy()
'api_platform.listener.view.serialize',
'api_platform.listener.view.validate',
'api_platform.listener.view.respond',
+ 'api_platform.serializer.normalizer.item',
'api_platform.serializer.context_builder',
'api_platform.doctrine.metadata_factory',
'api_platform.doctrine.orm.collection_data_provider',
diff --git a/tests/Hal/Serializer/ItemNormalizerTest.php b/tests/Hal/Serializer/ItemNormalizerTest.php
index 4e12ac1b9aa..c786a3d429e 100644
--- a/tests/Hal/Serializer/ItemNormalizerTest.php
+++ b/tests/Hal/Serializer/ItemNormalizerTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace ApiPlatform\Core\tests\Hal;
+namespace ApiPlatform\Core\Tests\Hal;
use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
diff --git a/tests/Serializer/ItemNormalizerTest.php b/tests/Serializer/ItemNormalizerTest.php
new file mode 100644
index 00000000000..4069571eb3e
--- /dev/null
+++ b/tests/Serializer/ItemNormalizerTest.php
@@ -0,0 +1,128 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ApiPlatform\Core\Tests\Serializer;
+
+use ApiPlatform\Core\Api\IriConverterInterface;
+use ApiPlatform\Core\Api\ResourceClassResolverInterface;
+use ApiPlatform\Core\Exception\InvalidArgumentException;
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
+use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
+use ApiPlatform\Core\Serializer\ItemNormalizer;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
+use Prophecy\Argument;
+use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
+use Symfony\Component\Serializer\SerializerInterface;
+
+/**
+ * @author Kévin Dunglas
+ */
+class ItemNormalizerTest extends \PHPUnit_Framework_TestCase
+{
+ public function testSupportNormalization()
+ {
+ $std = new \stdClass();
+ $dummy = new Dummy();
+ $dummy->setDescription('hello');
+
+ $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
+ $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
+ $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
+
+ $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
+ $resourceClassResolverProphecy->getResourceClass($dummy)->willReturn(Dummy::class)->shouldBeCalled();
+ $resourceClassResolverProphecy->getResourceClass($std)->willThrow(new InvalidArgumentException())->shouldBeCalled();
+ $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true)->shouldBeCalled();
+ $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false)->shouldBeCalled();
+
+ $normalizer = new ItemNormalizer(
+ $propertyNameCollectionFactoryProphecy->reveal(),
+ $propertyMetadataFactoryProphecy->reveal(),
+ $iriConverterProphecy->reveal(),
+ $resourceClassResolverProphecy->reveal()
+ );
+
+ $this->assertTrue($normalizer->supportsNormalization($dummy));
+ $this->assertTrue($normalizer->supportsNormalization($dummy));
+ $this->assertFalse($normalizer->supportsNormalization($std));
+
+ $this->assertTrue($normalizer->supportsDenormalization($dummy, Dummy::class));
+ $this->assertTrue($normalizer->supportsDenormalization($dummy, Dummy::class));
+ $this->assertFalse($normalizer->supportsDenormalization($std, \stdClass::class));
+ }
+
+ public function testNormalize()
+ {
+ $dummy = new Dummy();
+ $dummy->setName('hello');
+
+ $propertyNameCollection = new PropertyNameCollection(['name']);
+ $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
+ $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled();
+
+ $propertyMetadataFactory = new PropertyMetadata(null, null, true);
+ $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
+ $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadataFactory)->shouldBeCalled();
+
+ $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
+ $iriConverterProphecy->getIriFromItem($dummy)->willReturn('/dummies/12')->shouldBeCalled();
+
+ $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
+ $resourceClassResolverProphecy->getResourceClass($dummy, null, true)->willReturn(Dummy::class)->shouldBeCalled();
+
+ $serializerProphecy = $this->prophesize(SerializerInterface::class);
+ $serializerProphecy->willImplement(NormalizerInterface::class);
+ $serializerProphecy->normalize('hello', null, Argument::type('array'))->willReturn('hello')->shouldBeCalled();
+
+ $normalizer = new ItemNormalizer(
+ $propertyNameCollectionFactoryProphecy->reveal(),
+ $propertyMetadataFactoryProphecy->reveal(),
+ $iriConverterProphecy->reveal(),
+ $resourceClassResolverProphecy->reveal()
+ );
+ $normalizer->setSerializer($serializerProphecy->reveal());
+
+ $this->assertEquals(['id' => '/dummies/12', 'name' => 'hello'], $normalizer->normalize($dummy));
+ }
+
+ public function testDenormalize()
+ {
+ $context = ['resource_class' => Dummy::class];
+
+ $propertyNameCollection = new PropertyNameCollection(['name']);
+ $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
+ $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled();
+
+ $propertyMetadataFactory = new PropertyMetadata(null, null, true);
+ $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
+ $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadataFactory)->shouldBeCalled();
+
+ $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
+
+ $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
+
+ $serializerProphecy = $this->prophesize(SerializerInterface::class);
+ $serializerProphecy->willImplement(DenormalizerInterface::class);
+
+ $normalizer = new ItemNormalizer(
+ $propertyNameCollectionFactoryProphecy->reveal(),
+ $propertyMetadataFactoryProphecy->reveal(),
+ $iriConverterProphecy->reveal(),
+ $resourceClassResolverProphecy->reveal()
+ );
+ $normalizer->setSerializer($serializerProphecy->reveal());
+
+ $this->assertInstanceOf(Dummy::class, $normalizer->denormalize(['id' => '/dummies/12', 'name' => 'hello'], Dummy::class, null, $context));
+ }
+}