Skip to content

fix #857 - fix on put when both @id and id are present #877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 25, 2017
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
60 changes: 59 additions & 1 deletion features/main/custom_normalized.feature
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,67 @@ Feature: Using custom normalized entity
"@context": "/contexts/CustomNormalizedDummy",
"@id": "/custom_normalized_dummies/1",
"@type": "CustomNormalizedDummy",
"id": 1,
"name": "My Dummy",
"alias": "My alias"
}
"""

Scenario: Get a resource
Scenario: Create a resource with a custom normalized dummy
When I add "Content-Type" header equal to "application/json"
When I add "Accept" header equal to "application/json"
And I send a "POST" request to "/related_normalized_dummies" with body:
"""
{
"name": "My Dummy"
}
"""
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 JSON should be equal to:
"""
{
"id": 1,
"name": "My Dummy",
"customNormalizedDummy": []
}
"""

Scenario: Create a resource with a custom normalized dummy and an id
When I add "Content-Type" header equal to "application/json"
When I add "Accept" header equal to "application/json"
And I send a "PUT" request to "/related_normalized_dummies/1" with body:
"""
{
"name": "My Dummy",
"customNormalizedDummy":[{
"@context": "/contexts/CustomNormalizedDummy",
"@id": "/custom_normalized_dummies/1",
"@type": "CustomNormalizedDummy",
"id": 1,
"name": "My 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/json; charset=utf-8"
And the JSON should be equal to:
"""
{
"id": 1,
"name": "My Dummy",
"customNormalizedDummy":[{
"id": 1,
"name": "My Dummy",
"alias": "My alias"
}]
}
"""


Scenario: Get a custom normalized dummy resource
When I send a "GET" request to "/custom_normalized_dummies/1"
Then the response status code should be 200
And the response should be in JSON
Expand All @@ -38,6 +93,7 @@ Feature: Using custom normalized entity
"@context": "/contexts/CustomNormalizedDummy",
"@id": "/custom_normalized_dummies/1",
"@type": "CustomNormalizedDummy",
"id": 1,
"name": "My Dummy",
"alias": "My alias"
}
Expand All @@ -58,6 +114,7 @@ Feature: Using custom normalized entity
{
"@id": "/custom_normalized_dummies/1",
"@type": "CustomNormalizedDummy",
"id": 1,
"name": "My Dummy",
"alias": "My alias"
}
Expand All @@ -83,6 +140,7 @@ Feature: Using custom normalized entity
"@context": "/contexts/CustomNormalizedDummy",
"@id": "/custom_normalized_dummies/1",
"@type": "CustomNormalizedDummy",
"id": 1,
"name": "My Dummy modified",
"alias": "My alias"
}
Expand Down
23 changes: 22 additions & 1 deletion src/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,30 @@ public function denormalize($data, $class, $format = null, array $context = [])
throw new InvalidArgumentException('Update is not allowed for this operation.');
}

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

return parent::denormalize($data, $class, $format, $context);
}

private function updateObjectToPopulate(array $data, array &$context)
{
try {
$context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], $context + ['fetch_data' => false]);
} catch (InvalidArgumentException $e) {
$identifier = null;
foreach ($this->propertyNameCollectionFactory->create($context['resource_class'], $context) as $propertyName) {
if (true === $this->propertyMetadataFactory->create($context['resource_class'], $propertyName)->isIdentifier()) {
$identifier = $propertyName;
break;
}
}

if (null === $identifier) {
throw $e;
}

$context['object_to_populate'] = $this->iriConverter->getItemFromIri(sprintf('%s/%s', $this->iriConverter->getIriFromResourceClass($context['resource_class']), $data[$identifier]), $context + ['fetch_data' => false]);
}
}
}
1 change: 1 addition & 0 deletions tests/Fixtures/TestBundle/Entity/CustomNormalizedDummy.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class CustomNormalizedDummy
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"input", "output"})
*/
private $id;

Expand Down
6 changes: 3 additions & 3 deletions tests/Fixtures/TestBundle/Entity/DummyFriend.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class DummyFriend
/**
* Get id.
*
* @return id
* @return int
*/
public function getId()
{
Expand All @@ -69,7 +69,7 @@ public function setId($id)
/**
* Get name.
*
* @return name
* @return string
*/
public function getName()
{
Expand All @@ -79,7 +79,7 @@ public function getName()
/**
* Set name.
*
* @param name the value to set
* @param string the value to set
*/
public function setName($name)
{
Expand Down
100 changes: 100 additions & 0 deletions tests/Fixtures/TestBundle/Entity/RelatedNormalizedDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

/**
* Related to Normalized Dummy.
*
* @author Amrouche Hamza <[email protected]>
*
* @ApiResource(attributes={
* "normalization_context"={"groups"={"related_output", "output"}},
* "denormalization_context"={"groups"={"related_input", "input"}}
* })
* @ORM\Entity
*/
class RelatedNormalizedDummy
{
/**
* @var int The id
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"related_output", "related_input"})
*/
private $id;

/**
* @var string The dummy name
*
* @ORM\Column
* @Assert\NotBlank
* @ApiProperty(iri="http://schema.org/name")
* @Groups({"related_output", "related_input"})
*/
private $name;

/**
* @var ArrayCollection Several Normalized dummies
*
* @ORM\ManyToMany(targetEntity="CustomNormalizedDummy")
* @Groups({"related_output", "related_input"})
*/
public $customNormalizedDummy;

public function __construct()
{
$this->customNormalizedDummy = new ArrayCollection();
}

public function getId(): int
{
return $this->id;
}

/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}

public function getName(): string
{
return $this->name;
}

/**
* @return ArrayCollection
*/
public function getCustomNormalizedDummy()
{
return $this->customNormalizedDummy;
}

/**
* @param ArrayCollection $customNormalizedDummy
*/
public function setCustomNormalizedDummy($customNormalizedDummy)
{
$this->customNormalizedDummy = $customNormalizedDummy;
}
}
33 changes: 32 additions & 1 deletion tests/Serializer/ItemNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,37 @@ public function testDenormalize()
$this->assertInstanceOf(Dummy::class, $normalizer->denormalize(['name' => 'hello'], Dummy::class, null, $context));
}

public function testDenormalizeWithIri()
{
$context = ['resource_class' => Dummy::class, 'api_allow_update' => true];

$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->getItemFromIri('/dummies/12', ['resource_class' => Dummy::class, 'api_allow_update' => true, 'fetch_data' => false])->shouldBeCalled();

$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));
}

/**
* @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException
* @expectedExceptionMessage Update is not allowed for this operation.
Expand All @@ -151,6 +182,6 @@ public function testDenormalizeWithIdAndUpdateNotAllowed()
$resourceClassResolverProphecy->reveal()
);
$normalizer->setSerializer($serializerProphecy->reveal());
$normalizer->denormalize(['id' => '/dummies/12', 'name' => 'hello'], Dummy::class, null, $context);
$normalizer->denormalize(['id' => '12', 'name' => 'hello'], Dummy::class, null, $context);
}
}