Skip to content

Commit 31678f6

Browse files
committed
Add payload controller value resolver
1 parent 781dce7 commit 31678f6

File tree

11 files changed

+546
-11
lines changed

11 files changed

+546
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* MongoDB: `date_immutable` support (#3940)
1010
* DataProvider: Add `TraversablePaginator` (#3783)
1111
* Swagger UI: Add `swagger_ui_extra_configuration` to Swagger / OpenAPI configuration (#3731)
12+
* Allow controller argument with a name different from `$data` thanks to an argument resolver
1213

1314
## 2.6.3
1415

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Bridge\Symfony\Bundle\ArgumentResolver;
15+
16+
use ApiPlatform\Core\EventListener\ToggleableDeserializationTrait;
17+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
18+
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
19+
use ApiPlatform\Core\Util\RequestAttributesExtractor;
20+
use Symfony\Component\HttpFoundation\Request;
21+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
22+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
23+
24+
final class PayloadArgumentResolver implements ArgumentValueResolverInterface
25+
{
26+
use ToggleableDeserializationTrait;
27+
28+
private $serializationContextBuilder;
29+
30+
public function __construct(
31+
ResourceMetadataFactoryInterface $resourceMetadataFactory,
32+
SerializerContextBuilderInterface $serializationContextBuilder
33+
) {
34+
$this->resourceMetadataFactory = $resourceMetadataFactory;
35+
$this->serializationContextBuilder = $serializationContextBuilder;
36+
}
37+
38+
public function supports(Request $request, ArgumentMetadata $argument): bool
39+
{
40+
if ($argument->isVariadic()) {
41+
return false;
42+
}
43+
44+
$class = $argument->getType();
45+
46+
if (null === $class) {
47+
return false;
48+
}
49+
50+
if (null === $request->attributes->get('data')) {
51+
return false;
52+
}
53+
54+
$inputClass = $this->getExpectedInputClass($request);
55+
56+
if (null === $inputClass) {
57+
return false;
58+
}
59+
60+
return $inputClass === $class || is_subclass_of($inputClass, $class);
61+
}
62+
63+
public function resolve(Request $request, ArgumentMetadata $argument): \Generator
64+
{
65+
if (!$this->supports($request, $argument)) {
66+
throw new \InvalidArgumentException('Given request and argument not supported.');
67+
}
68+
69+
yield $request->attributes->get('data');
70+
}
71+
72+
private function getExpectedInputClass(Request $request): ?string
73+
{
74+
$attributes = RequestAttributesExtractor::extractAttributes($request);
75+
76+
if (!$this->isRequestToDeserialize($request, $attributes)) {
77+
return null;
78+
}
79+
80+
$context = $this->serializationContextBuilder->createFromRequest($request, false, $attributes);
81+
82+
return $context['input'] ?? $context['resource_class'];
83+
}
84+
}

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public function load(array $configs, ContainerBuilder $container): void
125125
$this->registerElasticsearchConfiguration($container, $config, $loader);
126126
$this->registerDataTransformerConfiguration($container);
127127
$this->registerSecurityConfiguration($container, $loader);
128+
$this->registerArgumentResolverConfiguration($container, $loader);
128129

129130
$container->registerForAutoconfiguration(DataPersisterInterface::class)
130131
->addTag('api_platform.data_persister');
@@ -717,6 +718,11 @@ private function registerOpenApiConfiguration(ContainerBuilder $container, array
717718
$container->setParameter('api_platform.openapi.license.url', $config['openapi']['license']['url']);
718719
}
719720

721+
private function registerArgumentResolverConfiguration(ContainerBuilder $container, XmlFileLoader $loader): void
722+
{
723+
$loader->load('argument_resolver.xml');
724+
}
725+
720726
private function buildDeprecationArgs(string $version, string $message): array
721727
{
722728
return method_exists(Definition::class, 'getDeprecation')
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="api_platform.payload_argument_resolver" class="ApiPlatform\Core\Bridge\Symfony\Bundle\ArgumentResolver\PayloadArgumentResolver" public="false">
9+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
10+
<argument type="service" id="api_platform.serializer.context_builder" />
11+
12+
<tag name="controller.argument_value_resolver" />
13+
</service>
14+
</services>
15+
16+
</container>

src/EventListener/DeserializeListener.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use ApiPlatform\Core\Api\FormatMatcher;
1717
use ApiPlatform\Core\Api\FormatsProviderInterface;
1818
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
19-
use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait;
2019
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
2120
use ApiPlatform\Core\Util\RequestAttributesExtractor;
2221
use Symfony\Component\HttpFoundation\Request;
@@ -32,7 +31,7 @@
3231
*/
3332
final class DeserializeListener
3433
{
35-
use ToggleableOperationAttributeTrait;
34+
use ToggleableDeserializationTrait;
3635

3736
public const OPERATION_ATTRIBUTE_KEY = 'deserialize';
3837

@@ -69,15 +68,9 @@ public function __construct(SerializerInterface $serializer, SerializerContextBu
6968
public function onKernelRequest(RequestEvent $event): void
7069
{
7170
$request = $event->getRequest();
72-
$method = $request->getMethod();
73-
74-
if (
75-
'DELETE' === $method
76-
|| $request->isMethodSafe()
77-
|| !($attributes = RequestAttributesExtractor::extractAttributes($request))
78-
|| !$attributes['receive']
79-
|| $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY)
80-
) {
71+
$attributes = RequestAttributesExtractor::extractAttributes($request);
72+
73+
if (!$this->isRequestToDeserialize($request, $attributes)) {
8174
return;
8275
}
8376

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\EventListener;
15+
16+
use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait;
17+
use Symfony\Component\HttpFoundation\Request;
18+
19+
trait ToggleableDeserializationTrait
20+
{
21+
use ToggleableOperationAttributeTrait;
22+
23+
private function isRequestToDeserialize(Request $request, array $attributes): bool
24+
{
25+
return
26+
'DELETE' !== $request->getMethod()
27+
&& !$request->isMethodSafe()
28+
&& [] !== $attributes
29+
&& $attributes['receive']
30+
&& !$this->isOperationAttributeDisabled($attributes, DeserializeListener::OPERATION_ATTRIBUTE_KEY);
31+
}
32+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Tests\Bridge\Symfony\Bundle\ArgumentResolver;
15+
16+
class NotResource
17+
{
18+
}

0 commit comments

Comments
 (0)