Skip to content

Commit

Permalink
Merge d6469a2 into 0a69709
Browse files Browse the repository at this point in the history
  • Loading branch information
jewome62 committed Jun 26, 2018
2 parents 0a69709 + d6469a2 commit b0fd1fa
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 14 deletions.
4 changes: 4 additions & 0 deletions features/main/crud.feature
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Feature: Create-Retrieve-Update-Delete
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/dummies/1"
And the header "Location" should be equal to "/dummies/1"
And the JSON should be equal to:
"""
{
Expand Down Expand Up @@ -420,6 +422,7 @@ Feature: Create-Retrieve-Update-Delete
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/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/dummies/1"
And the JSON should be equal to:
"""
{
Expand Down Expand Up @@ -457,6 +460,7 @@ Feature: Create-Retrieve-Update-Delete
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/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/dummies/1"
And the JSON should be equal to:
"""
{
Expand Down
3 changes: 3 additions & 0 deletions features/main/crud_abstract.feature
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Feature: Create-Retrieve-Update-Delete on abstract resource
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/concrete_dummies/1"
And the header "Location" should be equal to "/concrete_dummies/1"
And the JSON should be equal to:
"""
{
Expand Down Expand Up @@ -90,6 +92,7 @@ Feature: Create-Retrieve-Update-Delete on abstract resource
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/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/concrete_dummies/1"
And the JSON should be equal to:
"""
{
Expand Down
6 changes: 6 additions & 0 deletions features/main/custom_normalized.feature
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Feature: Using custom normalized entity
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/custom_normalized_dummies/1"
And the header "Location" should be equal to "/custom_normalized_dummies/1"
And the JSON should be equal to:
"""
{
Expand All @@ -40,6 +42,8 @@ Feature: Using custom normalized entity
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json; charset=utf-8"
And the header "Content-Location" should be equal to "/related_normalized_dummies/1"
And the header "Location" should be equal to "/related_normalized_dummies/1"
And the JSON should be equal to:
"""
{
Expand Down Expand Up @@ -68,6 +72,7 @@ Feature: Using custom normalized entity
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/json; charset=utf-8"
And the header "Content-Location" should be equal to "/related_normalized_dummies/1"
And the JSON should be equal to:
"""
{
Expand Down Expand Up @@ -134,6 +139,7 @@ Feature: Using custom normalized entity
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/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/custom_normalized_dummies/1"
And the JSON should be equal to:
"""
{
Expand Down
3 changes: 3 additions & 0 deletions features/main/custom_writable_identifier.feature
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Feature: Using custom writable identifier on resource
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/custom_writable_identifier_dummies/my_slug"
And the header "Location" should be equal to "/custom_writable_identifier_dummies/my_slug"
And the JSON should be equal to:
"""
{
Expand Down Expand Up @@ -78,6 +80,7 @@ Feature: Using custom writable identifier on resource
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/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/custom_writable_identifier_dummies/slug_modified"
And the JSON should be equal to:
"""
{
Expand Down
5 changes: 5 additions & 0 deletions features/main/uuid.feature
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Feature: Using uuid identifier on resource
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78"
And the header "Location" should be equal to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78"

Scenario: Get a resource
When I send a "GET" request to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78"
Expand Down Expand Up @@ -67,6 +69,7 @@ Feature: Using uuid identifier on resource
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/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78"
And the JSON should be equal to:
"""
{
Expand All @@ -87,6 +90,8 @@ Feature: Using uuid identifier on resource
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "Content-Location" should be equal to "/custom_generated_identifiers/foo"
And the header "Location" should be equal to "/custom_generated_identifiers/foo"
And the JSON should be equal to:
"""
{
Expand Down
1 change: 1 addition & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@

<service id="api_platform.listener.view.write" class="ApiPlatform\Core\EventListener\WriteListener">
<argument type="service" id="api_platform.data_persister" />
<argument type="service" id="api_platform.iri_converter" on-invalid="null" />

<tag name="kernel.event_listener" event="kernel.view" method="onKernelView" priority="32" />
</service>
Expand Down
26 changes: 20 additions & 6 deletions src/EventListener/RespondListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,29 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
return;
}

$headers = [
'Content-Type' => sprintf('%s; charset=utf-8', $request->getMimeType($request->getRequestFormat())),
'Vary' => 'Accept',
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'deny',
];

if (\in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'])) {
if ($request->attributes->has('_api_write_item_iri')) {
$headers['Content-Location'] = $request->attributes->get('_api_write_item_iri');

if ($request->isMethod('POST')) {
$headers['Location'] = $request->attributes->get('_api_write_item_iri');
}
} else {
@trigger_error(sprintf('No request attribute from `_api_write_item_iri` key is deprecated since API Platform 2.3 and will not be supported in API Platform 3, an string should always be returned. see deprecated into %s constructor for more details.', WriteListener::class), E_USER_DEPRECATED);
}
}

$event->setResponse(new Response(
$controllerResult,
self::METHOD_TO_CODE[$request->getMethod()] ?? Response::HTTP_OK,
[
'Content-Type' => sprintf('%s; charset=utf-8', $request->getMimeType($request->getRequestFormat())),
'Vary' => 'Accept',
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'deny',
]
$headers
));
}
}
14 changes: 13 additions & 1 deletion src/EventListener/WriteListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,30 @@

namespace ApiPlatform\Core\EventListener;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;

/**
* Bridges persistense and the API system.
*
* @final
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Baptiste Meyer <baptiste.meyer@gmail.com>
*/
class WriteListener
{
private $dataPersister;
private $iriConverter;

public function __construct(DataPersisterInterface $dataPersister)
public function __construct(DataPersisterInterface $dataPersister, IriConverterInterface $iriConverter = null)
{
$this->dataPersister = $dataPersister;
$this->iriConverter = $iriConverter;

if (null === $iriConverter) {
@trigger_error(sprintf('Class %s will have a second `IriConverterInterface $iriConverter` argument in version 3.0. Not defining it is deprecated since 2.3.', __CLASS__), E_USER_DEPRECATED);
}
}

/**
Expand Down Expand Up @@ -57,6 +65,10 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
}

$event->setControllerResult($persistResult ?? $controllerResult);

if (null !== $this->iriConverter) {
$request->attributes->set('_api_write_item_iri', $this->iriConverter->getIriFromItem($controllerResult));
}
break;
case 'DELETE':
$this->dataPersister->remove($controllerResult);
Expand Down
4 changes: 3 additions & 1 deletion tests/EventListener/RespondListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function testCreate201Response()
{
$kernelProphecy = $this->prophesize(HttpKernelInterface::class);

$request = new Request([], [], ['_api_respond' => true]);
$request = new Request([], [], ['_api_respond' => true, '_api_write_item_iri' => '/dummy_entities/1']);
$request->setMethod('POST');
$request->setRequestFormat('xml');

Expand All @@ -88,6 +88,8 @@ public function testCreate201Response()
$this->assertEquals('Accept', $response->headers->get('Vary'));
$this->assertEquals('nosniff', $response->headers->get('X-Content-Type-Options'));
$this->assertEquals('deny', $response->headers->get('X-Frame-Options'));
$this->assertEquals('/dummy_entities/1', $response->headers->get('Location'));
$this->assertEquals('/dummy_entities/1', $response->headers->get('Content-Location'));
}

public function testCreate204Response()
Expand Down
33 changes: 27 additions & 6 deletions tests/EventListener/WriteListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\Core\Tests\EventListener;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use ApiPlatform\Core\EventListener\WriteListener;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
Expand All @@ -35,6 +36,9 @@ public function testOnKernelViewWithControllerResultAndPersist()
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();
$dataPersisterProphecy->persist($dummy)->willReturn($dummy)->shouldBeCalled();

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->willReturn('/dummy/1')->shouldBeCalled();

$request = new Request();
$request->attributes->set('_api_resource_class', Dummy::class);

Expand All @@ -48,8 +52,9 @@ public function testOnKernelViewWithControllerResultAndPersist()
foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) {
$request->setMethod($httpMethod);

(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
$this->assertSame($dummy, $event->getControllerResult());
$this->assertEquals('/dummy/1', $request->attributes->get('_api_write_item_iri'));
}
}

Expand Down Expand Up @@ -98,6 +103,9 @@ public function testOnKernelViewWithControllerResultAndPersistWithImmutableResou
$dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class);
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->willReturn('/dummy/1')->shouldBeCalled();

$dataPersisterProphecy
->persist($dummy)
->willReturn($dummy2) // Persist is not mutating $dummy, but return a brand new technically unrelated object instead
Expand All @@ -117,9 +125,10 @@ public function testOnKernelViewWithControllerResultAndPersistWithImmutableResou

$request->setMethod($httpMethod);

(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);

$this->assertSame($dummy2, $event->getControllerResult());
$this->assertEquals('/dummy/1', $request->attributes->get('_api_write_item_iri'));
}
}

Expand All @@ -132,6 +141,9 @@ public function testOnKernelViewWithControllerResultAndRemove()
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();
$dataPersisterProphecy->remove($dummy)->shouldBeCalled();

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();

$request = new Request();
$request->setMethod('DELETE');
$request->attributes->set('_api_resource_class', Dummy::class);
Expand All @@ -143,7 +155,7 @@ public function testOnKernelViewWithControllerResultAndRemove()
$dummy
);

(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
}

public function testOnKernelViewWithSafeMethod()
Expand All @@ -156,6 +168,9 @@ public function testOnKernelViewWithSafeMethod()
$dataPersisterProphecy->persist($dummy)->shouldNotBeCalled();
$dataPersisterProphecy->remove($dummy)->shouldNotBeCalled();

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();

$request = new Request();
$request->setMethod('HEAD');
$request->attributes->set('_api_resource_class', Dummy::class);
Expand All @@ -167,7 +182,7 @@ public function testOnKernelViewWithSafeMethod()
$dummy
);

(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
}

public function testOnKernelViewWithNoResourceClass()
Expand All @@ -180,6 +195,9 @@ public function testOnKernelViewWithNoResourceClass()
$dataPersisterProphecy->persist($dummy)->shouldNotBeCalled();
$dataPersisterProphecy->remove($dummy)->shouldNotBeCalled();

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();

$request = new Request();
$request->setMethod('POST');

Expand All @@ -190,7 +208,7 @@ public function testOnKernelViewWithNoResourceClass()
$dummy
);

(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
}

public function testOnKernelViewWithNoDataPersisterSupport()
Expand All @@ -203,6 +221,9 @@ public function testOnKernelViewWithNoDataPersisterSupport()
$dataPersisterProphecy->persist($dummy)->shouldNotBeCalled();
$dataPersisterProphecy->remove($dummy)->shouldNotBeCalled();

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();

$request = new Request();
$request->setMethod('POST');
$request->attributes->set('_api_resource_class', 'Dummy');
Expand All @@ -214,6 +235,6 @@ public function testOnKernelViewWithNoDataPersisterSupport()
$dummy
);

(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
}
}

0 comments on commit b0fd1fa

Please sign in to comment.