Skip to content

Commit

Permalink
Add payload controller value resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
julienfalque authored and alanpoulain committed Jun 23, 2021
1 parent e19c9be commit 96b854b
Show file tree
Hide file tree
Showing 11 changed files with 507 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* DataProvider: Add `TraversablePaginator` (#3783)
* JSON:API: Support inclusion of resources from path (#3288)
* Swagger UI: Add `swagger_ui_extra_configuration` to Swagger / OpenAPI configuration (#3731)
* Allow controller argument with a name different from `$data` thanks to an argument resolver (#3263)
* GraphQL: Support `ApiProperty` security (#4143)
* GraphQL: **BC** Fix security on association collection properties. The collection resource `item_query` security is no longer used. `ApiProperty` security can now be used to secure collection (or any other) properties. (#4143)
* Deprecate `allow_plain_identifiers` option (#4167)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?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.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Bridge\Symfony\Bundle\ArgumentResolver;

use ApiPlatform\Core\EventListener\ToggleableDeserializationTrait;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use ApiPlatform\Core\Util\RequestAttributesExtractor;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

final class PayloadArgumentResolver implements ArgumentValueResolverInterface
{
use ToggleableDeserializationTrait;

private $serializationContextBuilder;

public function __construct(
ResourceMetadataFactoryInterface $resourceMetadataFactory,
SerializerContextBuilderInterface $serializationContextBuilder
) {
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->serializationContextBuilder = $serializationContextBuilder;
}

public function supports(Request $request, ArgumentMetadata $argument): bool
{
if ($argument->isVariadic()) {
return false;
}

$class = $argument->getType();

if (null === $class) {
return false;
}

if (null === $request->attributes->get('data')) {
return false;
}

$inputClass = $this->getExpectedInputClass($request);

if (null === $inputClass) {
return false;
}

return $inputClass === $class || is_subclass_of($inputClass, $class);
}

public function resolve(Request $request, ArgumentMetadata $argument): \Generator
{
yield $request->attributes->get('data');
}

private function getExpectedInputClass(Request $request): ?string
{
$attributes = RequestAttributesExtractor::extractAttributes($request);

if (!$this->isRequestToDeserialize($request, $attributes)) {
return null;
}

$context = $this->serializationContextBuilder->createFromRequest($request, false, $attributes);

return $context['input'] ?? $context['resource_class'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ public function load(array $configs, ContainerBuilder $container): void
$this->registerDataTransformerConfiguration($container);
$this->registerSecurityConfiguration($container, $loader);
$this->registerMakerConfiguration($container, $config, $loader);
$this->registerArgumentResolverConfiguration($container, $loader);

$container->registerForAutoconfiguration(DataPersisterInterface::class)
->addTag('api_platform.data_persister');
Expand Down Expand Up @@ -753,6 +754,11 @@ private function registerMakerConfiguration(ContainerBuilder $container, array $
$loader->load('maker.xml');
}

private function registerArgumentResolverConfiguration(ContainerBuilder $container, XmlFileLoader $loader): void
{
$loader->load('argument_resolver.xml');
}

private function buildDeprecationArgs(string $version, string $message): array
{
return method_exists(Definition::class, 'getDeprecation')
Expand Down
16 changes: 16 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/argument_resolver.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="api_platform.argument_resolver.payload" class="ApiPlatform\Core\Bridge\Symfony\Bundle\ArgumentResolver\PayloadArgumentResolver" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.serializer.context_builder" />

<tag name="controller.argument_value_resolver" />
</service>
</services>

</container>
15 changes: 4 additions & 11 deletions src/EventListener/DeserializeListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use ApiPlatform\Core\Api\FormatMatcher;
use ApiPlatform\Core\Api\FormatsProviderInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use ApiPlatform\Core\Util\RequestAttributesExtractor;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -32,7 +31,7 @@
*/
final class DeserializeListener
{
use ToggleableOperationAttributeTrait;
use ToggleableDeserializationTrait;

public const OPERATION_ATTRIBUTE_KEY = 'deserialize';

Expand Down Expand Up @@ -69,15 +68,9 @@ public function __construct(SerializerInterface $serializer, SerializerContextBu
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$method = $request->getMethod();

if (
'DELETE' === $method
|| $request->isMethodSafe()
|| !($attributes = RequestAttributesExtractor::extractAttributes($request))
|| !$attributes['receive']
|| $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY)
) {
$attributes = RequestAttributesExtractor::extractAttributes($request);

if (!$this->isRequestToDeserialize($request, $attributes)) {
return;
}

Expand Down
32 changes: 32 additions & 0 deletions src/EventListener/ToggleableDeserializationTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\EventListener;

use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait;
use Symfony\Component\HttpFoundation\Request;

trait ToggleableDeserializationTrait
{
use ToggleableOperationAttributeTrait;

private function isRequestToDeserialize(Request $request, array $attributes): bool
{
return
'DELETE' !== $request->getMethod()
&& !$request->isMethodSafe()
&& [] !== $attributes
&& $attributes['receive']
&& !$this->isOperationAttributeDisabled($attributes, DeserializeListener::OPERATION_ATTRIBUTE_KEY);
}
}
18 changes: 18 additions & 0 deletions tests/Bridge/Symfony/Bundle/ArgumentResolver/NotResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?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.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Bridge\Symfony\Bundle\ArgumentResolver;

class NotResource
{
}
Loading

0 comments on commit 96b854b

Please sign in to comment.