Skip to content

Commit

Permalink
Merge branch '2.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Sep 25, 2019
2 parents 81d276c + 78478a7 commit 59fa1e2
Show file tree
Hide file tree
Showing 14 changed files with 338 additions and 29 deletions.
30 changes: 30 additions & 0 deletions features/main/patch.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Feature: Sending PATCH requets
As a client software developer
I need to be able to send partial updates

@createSchema
Scenario: Detect accepted patch formats
Given I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/patch_dummies" with body:
"""
{"name": "Hello"}
"""
When I add "Content-Type" header equal to "application/ld+json"
And I send a "GET" request to "/patch_dummies/1"
Then the header "Accept-Patch" should be equal to "application/merge-patch+json, application/vnd.api+json"

Scenario: Patch an item
When I add "Content-Type" header equal to "application/merge-patch+json"
And I send a "PATCH" request to "/patch_dummies/1" with body:
"""
{"name": "Patched"}
"""
Then the JSON node "name" should contain "Patched"

Scenario: Remove a property according to RFC 7386
When I add "Content-Type" header equal to "application/merge-patch+json"
And I send a "PATCH" request to "/patch_dummies/1" with body:
"""
{"name": null}
"""
Then the JSON node "name" should not exist
26 changes: 18 additions & 8 deletions src/Annotation/ApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
* @Attribute("input", type="mixed"),
* @Attribute("iri", type="string"),
* @Attribute("itemOperations", type="array"),
* @Attribute("maximumItemsPerPage", type="int"),
* @Attribute("mercure", type="mixed"),
* @Attribute("messenger", type="mixed"),
* @Attribute("normalizationContext", type="array"),
Expand All @@ -58,6 +57,8 @@
* @Attribute("paginationEnabled", type="bool"),
* @Attribute("paginationFetchJoinCollection", type="bool"),
* @Attribute("paginationItemsPerPage", type="int"),
* @Attribute("maximumItemsPerPage", type="int"),
* @Attribute("paginationMaximumItemsPerPage", type="int"),
* @Attribute("paginationPartial", type="bool"),
* @Attribute("paginationViaCursor", type="array"),
* @Attribute("routePrefix", type="string"),
Expand Down Expand Up @@ -198,13 +199,6 @@ final class ApiResource
*/
private $hydraContext;

/**
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
*
* @var int
*/
private $maximumItemsPerPage;

/**
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
*
Expand Down Expand Up @@ -275,6 +269,22 @@ final class ApiResource
*/
private $paginationItemsPerPage;

/**
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
*
* @var int
*
* @deprecated - Use $paginationMaximumItemsPerPage instead
*/
private $maximumItemsPerPage;

/**
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
*
* @var int
*/
private $paginationMaximumItemsPerPage;

/**
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
*
Expand Down
9 changes: 8 additions & 1 deletion src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,14 @@ private function getPagination(QueryBuilder $queryBuilder, string $resourceClass
}

if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $this->clientItemsPerPage, true)) {
$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', $this->maximumItemPerPage, true);
$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', null, true);

if (null !== $maxItemsPerPage) {
@trigger_error('The "maximum_items_per_page" option has been deprecated since API Platform 2.5 in favor of "pagination_maximum_items_per_page" and will be removed in API Platform 3.', E_USER_DEPRECATED);
}

$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_maximum_items_per_page', $maxItemsPerPage ?? $this->maximumItemPerPage, true);

$itemsPerPage = (int) $this->getPaginationParameter($request, $this->itemsPerPageParameterName, $itemsPerPage);
$itemsPerPage = (null !== $maxItemsPerPage && $itemsPerPage >= $maxItemsPerPage ? $maxItemsPerPage : $itemsPerPage);
}
Expand Down
1 change: 0 additions & 1 deletion src/Bridge/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@

<service id="api_platform.serializer.context_builder" class="ApiPlatform\Core\Serializer\SerializerContextBuilder" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument>%api_platform.patch_formats%</argument>
</service>
<service id="ApiPlatform\Core\Serializer\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />

Expand Down
8 changes: 6 additions & 2 deletions src/DataProvider/Pagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,15 @@ public function getLimit(string $resourceClass = null, string $operationName = n

if ($clientLimit) {
$limit = (int) $this->getParameterFromContext($context, $this->options['items_per_page_parameter_name'], $limit);
$maxItemsPerPage = $this->options['maximum_items_per_page'];
$maxItemsPerPage = null;

if (null !== $resourceClass) {
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', $maxItemsPerPage, true);
$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', null, true);
if (null !== $maxItemsPerPage) {
@trigger_error('The "maximum_items_per_page" option has been deprecated since API Platform 2.5 in favor of "pagination_maximum_items_per_page" and will be removed in API Platform 3.', E_USER_DEPRECATED);
}
$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_maximum_items_per_page', $maxItemsPerPage ?? $this->options['maximum_items_per_page'], true);
}

if (null !== $maxItemsPerPage && $limit > $maxItemsPerPage) {
Expand Down
29 changes: 28 additions & 1 deletion src/EventListener/RespondListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Core\EventListener;

use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use ApiPlatform\Core\Util\RequestAttributesExtractor;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
Expand Down Expand Up @@ -51,7 +52,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void

return;
}
if ($controllerResult instanceof Response || !($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond', false))) {
if ($controllerResult instanceof Response || !($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond'))) {
return;
}

Expand All @@ -78,6 +79,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void
$headers['Sunset'] = (new \DateTimeImmutable($sunset))->format(\DateTime::RFC1123);
}

$headers = $this->addAcceptPatchHeader($headers, $attributes, $resourceMetadata);
$status = $resourceMetadata->getOperationAttribute($attributes, 'status');
}

Expand All @@ -87,4 +89,29 @@ public function onKernelView(GetResponseForControllerResultEvent $event): void
$headers
));
}

private function addAcceptPatchHeader(array $headers, array $attributes, ResourceMetadata $resourceMetadata): array
{
if (!isset($attributes['item_operation_name'])) {
return $headers;
}

$patchMimeTypes = [];
foreach ($resourceMetadata->getItemOperations() as $operation) {
if ('PATCH' !== ($operation['method'] ?? '') || !isset($operation['input_formats'])) {
continue;
}

foreach ($operation['input_formats'] as $mimeTypes) {
foreach ($mimeTypes as $mimeType) {
$patchMimeTypes[] = $mimeType;
}
}
$headers['Accept-Patch'] = implode(', ', $patchMimeTypes);

return $headers;
}

return $headers;
}
}
20 changes: 11 additions & 9 deletions src/Serializer/SerializerContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@
final class SerializerContextBuilder implements SerializerContextBuilderInterface
{
private $resourceMetadataFactory;
private $patchFormats;

public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $patchFormats = [])
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory)
{
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->patchFormats = $patchFormats;
}

/**
Expand Down Expand Up @@ -91,12 +89,16 @@ public function createFromRequest(Request $request, bool $normalization, array $

unset($context[DocumentationNormalizer::SWAGGER_DEFINITION_NAME]);

if (
isset($this->patchFormats['json'])
&& !isset($context['skip_null_values'])
&& \in_array('application/merge-patch+json', $this->patchFormats['json'], true)
) {
$context['skip_null_values'] = true;
if (isset($context['skip_null_values'])) {
return $context;
}

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;

break;
}
}

return $context;
Expand Down
10 changes: 8 additions & 2 deletions src/Swagger/Serializer/DocumentationNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,14 @@ private function addPaginationParameters(bool $v3, ResourceMetadata $resourceMet
'minimum' => 0,
];

if ($maximumItemPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', false, true)) {
$itemPerPageParameter['schema']['maximum'] = $maximumItemPerPage;
$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', null, true);
if (null !== $maxItemsPerPage) {
@trigger_error('The "maximum_items_per_page" option has been deprecated since API Platform 2.5 in favor of "pagination_maximum_items_per_page" and will be removed in API Platform 3.', E_USER_DEPRECATED);
}
$maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_maximum_items_per_page', $maxItemsPerPage, true);

if (null !== $maxItemsPerPage) {
$itemPerPageParameter['schema']['maximum'] = $maxItemsPerPage;
}
} else {
$itemPerPageParameter['type'] = 'integer';
Expand Down
6 changes: 4 additions & 2 deletions tests/Annotation/ApiResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public function testConstruct()
'input' => 'Foo',
'iri' => 'http://example.com/res',
'itemOperations' => ['foo' => ['bar']],
'maximumItemsPerPage' => 42,
'mercure' => '[\'foo\', object.owner]',
'messenger' => true,
'normalizationContext' => ['groups' => ['bar']],
Expand All @@ -56,6 +55,8 @@ public function testConstruct()
'paginationEnabled' => true,
'paginationFetchJoinCollection' => true,
'paginationItemsPerPage' => 42,
'maximumItemsPerPage' => 42, // deprecated, see paginationMaximumItemsPerPage
'paginationMaximumItemsPerPage' => 50,
'paginationPartial' => true,
'routePrefix' => '/foo',
'shortName' => 'shortName',
Expand Down Expand Up @@ -84,7 +85,6 @@ public function testConstruct()
'formats' => ['foo', 'bar' => ['application/bar']],
'filters' => ['foo', 'bar'],
'input' => 'Foo',
'maximum_items_per_page' => 42,
'mercure' => '[\'foo\', object.owner]',
'messenger' => true,
'normalization_context' => ['groups' => ['bar']],
Expand All @@ -97,6 +97,8 @@ public function testConstruct()
'pagination_enabled' => true,
'pagination_fetch_join_collection' => true,
'pagination_items_per_page' => 42,
'maximum_items_per_page' => 42,
'pagination_maximum_items_per_page' => 50,
'pagination_partial' => true,
'route_prefix' => '/foo',
'swagger_context' => ['description' => 'bar'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,15 +318,15 @@ public function testApplyToCollectionWithMaximumItemsPerPage()
$attributes = [
'pagination_enabled' => true,
'pagination_client_enabled' => true,
'maximum_items_per_page' => 80,
'pagination_maximum_items_per_page' => 80,
];
$resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata(null, null, null, [], [], $attributes));
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();

$pagination = new Pagination($resourceMetadataFactory, [
'client_enabled' => true,
'client_items_per_page' => true,
'maximum_items_per_page' => 50,
'pagination_maximum_items_per_page' => 50,
]);

$aggregationBuilderProphecy = $this->mockAggregationBuilder(0, 80);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ public function testApplyToCollectionWithMaximumItemsPerPage()
$attributes = [
'pagination_enabled' => true,
'pagination_client_enabled' => true,
'maximum_items_per_page' => 80,
'pagination_maximum_items_per_page' => 80,
];
$resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata(null, null, null, [], [], $attributes));
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
Expand Down Expand Up @@ -709,6 +709,7 @@ public function testApplyToCollectionWithMaximumItemsPerPage()
* @expectedDeprecation Passing an instance of "Symfony\Component\HttpFoundation\RequestStack" as second argument of "ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\PaginationExtension" is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface" instead.
* @expectedDeprecation Passing an instance of "ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface" as third argument of "ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\PaginationExtension" is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "ApiPlatform\Core\DataProvider\Pagination" instead.
* @expectedDeprecation Passing "$enabled", "$clientEnabled", "$clientItemsPerPage", "$itemsPerPage", "$pageParameterName", "$enabledParameterName", "$itemsPerPageParameterName", "$maximumItemPerPage", "$partial", "$clientPartial", "$partialParameterName" arguments is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator" as third argument instead.
* @expectedDeprecation The "maximum_items_per_page" option has been deprecated since API Platform 2.5 in favor of "pagination_maximum_items_per_page" and will be removed in API Platform 3.
*/
public function testLegacyApplyToCollectionWithMaximumItemsPerPage()
{
Expand Down
41 changes: 41 additions & 0 deletions tests/Fixtures/TestBundle/Document/PatchDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?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;

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

/**
* @ODM\Field(type="string")
*/
public $name;
}
43 changes: 43 additions & 0 deletions tests/Fixtures/TestBundle/Entity/PatchDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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;

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

/**
* @ORM\Column(nullable=true)
*/
public $name;
}
Loading

0 comments on commit 59fa1e2

Please sign in to comment.