Skip to content

Commit 099fd09

Browse files
committed
Add payload controller value resolver
1 parent a12ac48 commit 099fd09

File tree

11 files changed

+503
-11
lines changed

11 files changed

+503
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 2.6.x-dev
44

55
* MongoDB: Possibility to add execute options (aggregate command fields) for a resource, like `allowDiskUse` (#3144)
6+
* Allow controller argument with a name different from `$data` thanks to an argument resolver
67

78
## 2.5.1
89

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
class PayloadArgumentResolver implements ArgumentValueResolverInterface
25+
{
26+
use ToggleableDeserializationTrait;
27+
28+
/**
29+
* @var SerializerContextBuilderInterface
30+
*/
31+
private $serializationContextBuilder;
32+
33+
public function __construct(
34+
ResourceMetadataFactoryInterface $resourceMetadataFactory,
35+
SerializerContextBuilderInterface $serializationContextBuilder
36+
) {
37+
$this->resourceMetadataFactory = $resourceMetadataFactory;
38+
$this->serializationContextBuilder = $serializationContextBuilder;
39+
}
40+
41+
public function supports(Request $request, ArgumentMetadata $argument): bool
42+
{
43+
if ($argument->isVariadic()) {
44+
return false;
45+
}
46+
47+
$class = $argument->getType();
48+
49+
if (null === $class) {
50+
return false;
51+
}
52+
53+
if (null === $request->attributes->get('data')) {
54+
return false;
55+
}
56+
57+
$inputClass = $this->getExpectedInputClass($request);
58+
59+
if (null === $inputClass) {
60+
return false;
61+
}
62+
63+
return $inputClass === $class || is_subclass_of($inputClass, $class);
64+
}
65+
66+
public function resolve(Request $request, ArgumentMetadata $argument): \Generator
67+
{
68+
if (!$this->supports($request, $argument)) {
69+
throw new \InvalidArgumentException('Given request and argument not supported.');
70+
}
71+
72+
yield $request->attributes->get('data');
73+
}
74+
75+
private function getExpectedInputClass(Request $request): ?string
76+
{
77+
$attributes = RequestAttributesExtractor::extractAttributes($request);
78+
79+
if (!$this->isRequestToDeserialize($request, $attributes)) {
80+
return null;
81+
}
82+
83+
$context = $this->serializationContextBuilder->createFromRequest($request, false, $attributes);
84+
85+
return $context['input'] ?? $context['resource_class'];
86+
}
87+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public function load(array $configs, ContainerBuilder $container): void
117117
$this->registerElasticsearchConfiguration($container, $config, $loader);
118118
$this->registerDataTransformerConfiguration($container);
119119
$this->registerSecurityConfiguration($container, $loader);
120+
$this->registerArgumentResolverConfiguration($container, $loader);
120121

121122
$container->registerForAutoconfiguration(DataPersisterInterface::class)
122123
->addTag('api_platform.data_persister');
@@ -632,4 +633,9 @@ private function registerSecurityConfiguration(ContainerBuilder $container, XmlF
632633
$loader->load('security.xml');
633634
}
634635
}
636+
637+
private function registerArgumentResolverConfiguration(ContainerBuilder $container, XmlFileLoader $loader): void
638+
{
639+
$loader->load('argument_resolver.xml');
640+
}
635641
}
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.graphql.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(false)
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)