Skip to content

Commit

Permalink
Merge 3f25711 into 5573b2a
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Nov 29, 2020
2 parents 5573b2a + 3f25711 commit 39716a4
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 2 deletions.
31 changes: 31 additions & 0 deletions features/main/patch.feature
Expand Up @@ -28,3 +28,34 @@ Feature: Sending PATCH requets
{"name": null}
"""
Then the JSON node "name" should not exist

@createSchema
Scenario: Patch the relation
Given there is a PatchDummyRelation
When I add "Content-Type" header equal to "application/merge-patch+json"
And I send a "PATCH" request to "/patch_dummy_relations/1" with body:
"""
{
"related": {
"symfony": "A new name"
}
}
"""
Then print last JSON response
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 JSON should be equal to:
"""
{
"@context": "/contexts/PatchDummyRelation",
"@id": "/patch_dummy_relations/1",
"@type": "PatchDummyRelation",
"related": {
"@id": "/related_dummies/1",
"@type": "https://schema.org/Product",
"id": 1,
"symfony": "A new name"
}
}
"""
4 changes: 4 additions & 0 deletions src/Serializer/AbstractItemNormalizer.php
Expand Up @@ -577,6 +577,10 @@ protected function getAttributeValue($object, $attribute, $format = null, array
$attributeValue = null;
}

if ($context['api_denormalize'] ?? false) {
return $attributeValue;
}

$type = $propertyMetadata->getType();

if (
Expand Down
9 changes: 7 additions & 2 deletions src/Serializer/SerializerContextBuilder.php
Expand Up @@ -63,7 +63,11 @@ public function createFromRequest(Request $request, bool $normalization, array $

if (!$normalization) {
if (!isset($context['api_allow_update'])) {
$context['api_allow_update'] = \in_array($request->getMethod(), ['PUT', 'PATCH'], true);
$context['api_allow_update'] = \in_array($method = $request->getMethod(), ['PUT', 'PATCH'], true);

if ($context['api_allow_update'] && 'PATCH' === $method) {
$context[AbstractItemNormalizer::DEEP_OBJECT_TO_POPULATE] = $context[AbstractItemNormalizer::DEEP_OBJECT_TO_POPULATE] ?? true;
}
}

if ('csv' === $request->getContentType()) {
Expand Down Expand Up @@ -101,9 +105,10 @@ public function createFromRequest(Request $request, bool $normalization, array $
return $context;
}

// TODO: We should always use `skip_null_values` but changing this would be a BC break, for now use it only when `merge-patch+json` is activated on a Resource
foreach ($resourceMetadata->getItemOperations() as $operation) {
if ('PATCH' === ($operation['method'] ?? '') && \in_array('application/merge-patch+json', $operation['input_formats']['json'] ?? [], true)) {
$context['skip_null_values'] = true;
$context[AbstractItemNormalizer::SKIP_NULL_VALUES] = true;

break;
}
Expand Down
23 changes: 23 additions & 0 deletions tests/Behat/DoctrineContext.php
Expand Up @@ -61,6 +61,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathDummy as NetworkPathDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathRelationDummy as NetworkPathRelationDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Order as OrderDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\PatchDummyRelation as PatchDummyRelationDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Person as PersonDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\PersonToPet as PersonToPetDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Pet as PetDocument;
Expand Down Expand Up @@ -128,6 +129,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathRelationDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Order;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\PatchDummyRelation;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Person;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\PersonToPet;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Pet;
Expand Down Expand Up @@ -1658,6 +1660,19 @@ public function thereIsAnInitializeInput(int $id)
$this->manager->flush();
}

/**
* @Given there is a PatchDummyRelation
*/
public function thereIsAPatchDummyRelation()
{
$dummy = $this->buildPatchDummyRelation();
$related = $this->buildRelatedDummy();
$dummy->setRelated($related);
$this->manager->persist($related);
$this->manager->persist($dummy);
$this->manager->flush();
}

private function isOrm(): bool
{
return null !== $this->schemaTool;
Expand Down Expand Up @@ -2091,4 +2106,12 @@ private function buildInitializeInput()
{
return $this->isOrm() ? new InitializeInput() : new InitializeInputDocument();
}

/**
* @return PatchDummyRelation|PatchDummyRelationDocument
*/
private function buildPatchDummyRelation()
{
return $this->isOrm() ? new PatchDummyRelation() : new PatchDummyRelationDocument();
}
}
57 changes: 57 additions & 0 deletions tests/Fixtures/TestBundle/Document/PatchDummyRelation.php
@@ -0,0 +1,57 @@
<?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.
*/

declare(strict_types=1);

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

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @ApiResource(
* attributes={
* "normalization_context"={"groups"={"chicago"}},
* "denormalization_context"={"groups"={"chicago"}},
* },
* itemOperations={
* "get",
* "patch"={"input_formats"={"json"={"application/merge-patch+json"}, "jsonapi"}}
* }
* )
* @ODM\Document
*/
class PatchDummyRelation
{
/**
* @ODM\Id(strategy="INCREMENT", type="integer")
*/
public $id;

/**
* @ODM\ReferenceOne(targetDocument=RelatedDummy::class)
* @Groups({"chicago"})
*/
protected $related;

public function getRelated()
{
return $this->related;
}

public function setRelated(RelatedDummy $relatedDummy)
{
$this->related = $relatedDummy;
}
}
59 changes: 59 additions & 0 deletions tests/Fixtures/TestBundle/Entity/PatchDummyRelation.php
@@ -0,0 +1,59 @@
<?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.
*/

declare(strict_types=1);

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

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @ApiResource(
* attributes={
* "normalization_context"={"groups"={"chicago"}},
* "denormalization_context"={"groups"={"chicago"}},
* },
* itemOperations={
* "get",
* "patch"={"input_formats"={"json"={"application/merge-patch+json"}, "jsonapi"}}
* }
* )
* @ORM\Entity
*/
class PatchDummyRelation
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
public $id;

/**
* @ORM\ManyToOne(targetEntity="RelatedDummy")
* @Groups({"chicago"})
*/
protected $related;

public function getRelated()
{
return $this->related;
}

public function setRelated(RelatedDummy $relatedDummy)
{
$this->related = $relatedDummy;
}
}
14 changes: 14 additions & 0 deletions tests/Serializer/SerializerContextBuilderTest.php
Expand Up @@ -48,8 +48,17 @@ protected function setUp(): void
]
);

$resourceMetadataWithPatch = new ResourceMetadata(
null,
null,
null,
['patch' => ['method' => 'PATCH', 'input_formats' => ['json' => ['application/merge-patch+json']]]],
[]
);

$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata);
$resourceMetadataFactoryProphecy->create('FooWithPatch')->willReturn($resourceMetadataWithPatch);

$this->builder = new SerializerContextBuilder($resourceMetadataFactoryProphecy->reveal());
}
Expand Down Expand Up @@ -85,6 +94,11 @@ public function testCreateFromRequest()
$request->attributes->replace(['_api_resource_class' => 'Foo', '_api_subresource_operation_name' => 'get', '_api_format' => 'xml', '_api_mime_type' => 'text/xml']);
$expected = ['bar' => 'baz', 'subresource_operation_name' => 'get', 'resource_class' => 'Foo', 'request_uri' => '/bars/1/foos', 'operation_type' => 'subresource', 'api_allow_update' => false, 'uri' => 'http://localhost/bars/1/foos', 'output' => null, 'input' => null, 'iri_only' => false];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));

$request = Request::create('/foowithpatch/1', 'PATCH');
$request->attributes->replace(['_api_resource_class' => 'FooWithPatch', '_api_item_operation_name' => 'patch', '_api_format' => 'json', '_api_mime_type' => 'application/json']);
$expected = ['item_operation_name' => 'patch', 'resource_class' => 'FooWithPatch', 'request_uri' => '/foowithpatch/1', 'operation_type' => 'item', 'api_allow_update' => true, 'uri' => 'http://localhost/foowithpatch/1', 'output' => null, 'input' => null, 'deep_object_to_populate' => true, 'skip_null_values' => true, 'iri_only' => false];
$this->assertEquals($expected, $this->builder->createFromRequest($request, false));
}

public function testThrowExceptionOnInvalidRequest()
Expand Down

0 comments on commit 39716a4

Please sign in to comment.