Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions features/content_negotiation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
<?xml version="1.0"?>
<response><id>1</id><name>XML!</name><alias/><description/><dummyDate/><dummyPrice/><jsonData/><relatedDummy/><dummyBoolean/><dummy/><relatedDummies/><nameConverted/></response>
<response><id>/dummies/1</id><description/><dummy/><dummyBoolean/><dummyDate/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><name_converted/><name>XML!</name><alias/></response>
"""

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

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

Scenario: Retrieve a collection in JSON
Expand All @@ -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
}
]
"""
Expand All @@ -79,7 +79,7 @@ Feature: Content Negotiation support
And the response should be equal to
"""
<?xml version="1.0"?>
<response><id>2</id><name>Sent in JSON</name><alias/><description/><dummyDate/><dummyPrice/><jsonData/><relatedDummy/><dummyBoolean/><dummy/><relatedDummies/><nameConverted/></response>
<response><id>/dummies/2</id><description/><dummy/><dummyBoolean/><dummyDate/><dummyPrice/><relatedDummy/><relatedDummies/><jsonData/><name_converted/><name>Sent in JSON</name><alias/></response>
"""

@dropSchema
Expand Down
21 changes: 12 additions & 9 deletions features/hal.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
8 changes: 4 additions & 4 deletions features/overridden_operation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
<?xml version="1.0"?>
<response><name>My Overridden Operation Dummy</name><alias/><description>Gerard</description></response>
<?xml version="1.0"?>
<response><id>/overridden_operation_dummies/1</id><name>My Overridden Operation Dummy</name><alias/><description>Gerard</description></response>
"""

Scenario: Get a not found exception
Expand All @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion features/relation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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}
"""
Expand Down
11 changes: 11 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
</service>

<service id="api_platform.serializer.normalizer.item" class="ApiPlatform\Core\Serializer\ItemNormalizer" public="false">
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="api_platform.resource_class_resolver" />
<argument type="service" id="api_platform.property_accessor" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />

<tag name="serializer.normalizer" />
</service>

<!-- Resource path naming strategies -->

<service id="api_platform.naming.resource_path_naming_strategy.underscore" class="ApiPlatform\Core\Naming\UnderscoreResourcePathNamingStrategy" public="false" />
Expand Down
2 changes: 1 addition & 1 deletion src/Bridge/Symfony/Bundle/Resources/config/hal.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<argument type="service" id="api_platform.property_accessor" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />

<tag name="serializer.normalizer" />
<tag name="serializer.normalizer" priority="8" />
</service>

<service id="api_platform.hal.normalizer.collection" class="ApiPlatform\Core\Hal\Serializer\CollectionNormalizer" public="false">
Expand Down
2 changes: 1 addition & 1 deletion src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<argument type="service" id="api_platform.property_accessor" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />

<tag name="serializer.normalizer" />
<tag name="serializer.normalizer" priority="8" />
</service>

<service id="api_platform.jsonld.encoder" class="ApiPlatform\Core\Serializer\JsonEncoder" public="false">
Expand Down
44 changes: 44 additions & 0 deletions src/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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 <dunglas@gmail.com>
*/
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'])) {
Copy link
Contributor

@theofidry theofidry Jul 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ID an IRI in all cases (i.e. all formats)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's better to always use an IRI as identifier but I'm open to discussion :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's alright, just wanted to make sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen a discussion about this #612

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Simperfit I don't think #612 is related.

$data['id'] = $this->iriConverter->getIriFromItem($object);
}

return array_merge($data, $rawData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no better way than an array_merge? Not sure about this but isn't iterating over the second array to add values to the first one faster?

Copy link
Member Author

@dunglas dunglas Jul 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want the ID as first key. Do you know a better way to that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does the array_merge guarantees that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does $data come from?

}

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO to be removed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion tests/Hal/Serializer/ItemNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
128 changes: 128 additions & 0 deletions tests/Serializer/ItemNormalizerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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 <dunglas@gmail.com>
*/
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));
}
}