Skip to content

Commit 3ff0d44

Browse files
committed
fix(state): query and header parameter with the same name
1 parent 0edc738 commit 3ff0d44

15 files changed

+205
-67
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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\Doctrine\Common;
15+
16+
trait ParameterValueExtractorTrait
17+
{
18+
/**
19+
* @param array<string, mixed> $values
20+
*
21+
* @return array<string, mixed>
22+
*/
23+
private function extractParameterValue(array $values): array
24+
{
25+
if (!$values) {
26+
return $values;
27+
}
28+
29+
$key = key($values);
30+
if (!str_contains($key, ':property')) {
31+
return $values;
32+
}
33+
34+
return [str_replace('[:property]', '', $key) => $values[$key]];
35+
}
36+
}

src/Doctrine/Odm/Extension/ParameterExtension.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Odm\Extension;
1515

16+
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
1617
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
1718
use ApiPlatform\Metadata\Operation;
1819
use Doctrine\ODM\MongoDB\Aggregation\Builder;
@@ -25,14 +26,16 @@
2526
*/
2627
final class ParameterExtension implements AggregationCollectionExtensionInterface, AggregationItemExtensionInterface
2728
{
29+
use ParameterValueExtractorTrait;
30+
2831
public function __construct(private readonly ContainerInterface $filterLocator)
2932
{
3033
}
3134

3235
private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass = null, ?Operation $operation = null, array &$context = []): void
3336
{
3437
foreach ($operation->getParameters() ?? [] as $parameter) {
35-
$values = $parameter->getExtraProperties()['_api_values'] ?? [];
38+
$values = $this->extractParameterValue($parameter->getValue() ?? []);
3639
if (!$values) {
3740
continue;
3841
}

src/Doctrine/Orm/Extension/ParameterExtension.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Orm\Extension;
1515

16+
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
1617
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
1718
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
1819
use ApiPlatform\Metadata\Operation;
@@ -26,6 +27,8 @@
2627
*/
2728
final class ParameterExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
2829
{
30+
use ParameterValueExtractorTrait;
31+
2932
public function __construct(private readonly ContainerInterface $filterLocator)
3033
{
3134
}
@@ -36,7 +39,7 @@ public function __construct(private readonly ContainerInterface $filterLocator)
3639
private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
3740
{
3841
foreach ($operation?->getParameters() ?? [] as $parameter) {
39-
$values = $parameter->getExtraProperties()['_api_values'] ?? [];
42+
$values = $this->extractParameterValue($parameter->getValue() ?? []);
4043
if (!$values) {
4144
continue;
4245
}

src/Metadata/Parameter.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public function getKey(): ?string
5050
}
5151

5252
/**
53-
* @return array{type?: string}|null $schema
53+
* @return (array<string, mixed>&array{type?: string, default?: string})|null $schema
5454
*/
5555
public function getSchema(): ?array
5656
{
@@ -100,6 +100,16 @@ public function getConstraints(): Constraint|array|null
100100
return $this->constraints;
101101
}
102102

103+
/**
104+
* The computed value of this parameter, located into extraProperties['_api_values'].
105+
*
106+
* @readonly
107+
*/
108+
public function getValue(): mixed
109+
{
110+
return $this->extraProperties['_api_values'] ?? null;
111+
}
112+
103113
/**
104114
* @return array<string, mixed>
105115
*/

src/Metadata/Parameters.php

+26-15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace ApiPlatform\Metadata;
1515

16+
use ApiPlatform\Metadata\Exception\RuntimeException;
17+
1618
/**
1719
* A parameter dictionnary.
1820
*
@@ -53,7 +55,7 @@ public function getIterator(): \Traversable
5355
public function add(string $key, Parameter $value): self
5456
{
5557
foreach ($this->parameters as $i => [$parameterName, $parameter]) {
56-
if ($parameterName === $key) {
58+
if ($parameterName === $key && $value::class === $parameter::class) {
5759
$this->parameters[$i] = [$key, $value];
5860

5961
return $this;
@@ -65,34 +67,43 @@ public function add(string $key, Parameter $value): self
6567
return $this;
6668
}
6769

68-
public function get(string $key): ?Parameter
70+
/**
71+
* @param class-string $parameterClass
72+
*/
73+
public function remove(string $key, string $parameterClass): self
6974
{
7075
foreach ($this->parameters as $i => [$parameterName, $parameter]) {
71-
if ($parameterName === $key) {
72-
return $parameter;
76+
if ($parameterName === $key && $parameterClass === $parameter::class) {
77+
unset($this->parameters[$i]);
78+
79+
return $this;
7380
}
7481
}
7582

76-
return null;
83+
throw new RuntimeException(sprintf('Could not remove parameter "%s".', $key));
7784
}
7885

79-
public function remove(string $key): self
86+
/**
87+
* @param class-string $parameterClass
88+
*/
89+
public function get(string $key, string $parameterClass): ?Parameter
8090
{
81-
foreach ($this->parameters as $i => [$parameterName, $parameter]) {
82-
if ($parameterName === $key) {
83-
unset($this->parameters[$i]);
84-
85-
return $this;
91+
foreach ($this->parameters as [$parameterName, $parameter]) {
92+
if ($parameterName === $key && $parameterClass === $parameter::class) {
93+
return $parameter;
8694
}
8795
}
8896

89-
throw new \RuntimeException(sprintf('Could not remove parameter "%s".', $key));
97+
return null;
9098
}
9199

92-
public function has(string $key): bool
100+
/**
101+
* @param class-string $parameterClass
102+
*/
103+
public function has(string $key, string $parameterClass): bool
93104
{
94-
foreach ($this->parameters as $i => [$parameterName, $parameter]) {
95-
if ($parameterName === $key) {
105+
foreach ($this->parameters as [$parameterName, $parameter]) {
106+
if ($parameterName === $key && $parameterClass === $parameter::class) {
96107
return true;
97108
}
98109
}

src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private function mergeOperationParameters(Metadata $resource, Parameters $global
245245
$parameterName = $key;
246246
}
247247

248-
if (!$parameters->has($parameterName)) {
248+
if (!$parameters->has($parameterName, $parameter::class)) {
249249
$parameters->add($parameterName, $parameter);
250250
}
251251
}

src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php

+16-12
Original file line numberDiff line numberDiff line change
@@ -55,34 +55,38 @@ public function create(string $resourceClass): ResourceMetadataCollection
5555

5656
$internalPriority = -1;
5757
foreach ($operations as $operationName => $operation) {
58-
$parameters = [];
59-
foreach ($operation->getParameters() ?? [] as $key => $parameter) {
58+
$parameters = $operation->getParameters() ?? new Parameters();
59+
foreach ($parameters as $key => $parameter) {
60+
$key = $parameter->getKey() ?? $key;
6061
$parameter = $this->setDefaults($key, $parameter, $resourceClass);
6162
$priority = $parameter->getPriority() ?? $internalPriority--;
62-
$parameters[$key] = $parameter->withPriority($priority);
63+
$parameters->add($key, $parameter->withPriority($priority));
6364
}
6465

65-
$operations->add($operationName, $operation->withParameters(new Parameters($parameters)));
66+
$operations->add($operationName, $operation->withParameters($parameters));
6667
}
6768

6869
$resourceMetadataCollection[$i] = $resource->withOperations($operations->sort());
6970

70-
$internalPriority = -1;
7171
$graphQlOperations = $resource->getGraphQlOperations();
72-
foreach ($graphQlOperations ?? [] as $operationName => $operation) {
73-
$parameters = [];
72+
if (!$graphQlOperations) {
73+
continue;
74+
}
75+
76+
$internalPriority = -1;
77+
foreach ($graphQlOperations as $operationName => $operation) {
78+
$parameters = $operation->getParameters() ?? new Parameters();
7479
foreach ($operation->getParameters() ?? [] as $key => $parameter) {
80+
$key = $parameter->getKey() ?? $key;
7581
$parameter = $this->setDefaults($key, $parameter, $resourceClass);
7682
$priority = $parameter->getPriority() ?? $internalPriority--;
77-
$parameters[$key] = $parameter->withPriority($priority);
83+
$parameters->add($key, $parameter->withPriority($priority));
7884
}
7985

80-
$graphQlOperations[$operationName] = $operation->withParameters(new Parameters($parameters));
86+
$graphQlOperations[$operationName] = $operation->withParameters($parameters);
8187
}
8288

83-
if ($graphQlOperations) {
84-
$resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
85-
}
89+
$resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
8690
}
8791

8892
return $resourceMetadataCollection;

src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTests.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Metadata\FilterInterface;
1717
use ApiPlatform\Metadata\Parameters;
18+
use ApiPlatform\Metadata\QueryParameter;
1819
use ApiPlatform\Metadata\Resource\Factory\AttributesResourceMetadataCollectionFactory;
1920
use ApiPlatform\Metadata\Resource\Factory\ParameterResourceMetadataCollectionFactory;
2021
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\WithParameter;
@@ -50,10 +51,10 @@ public function getDescription(string $resourceClass): array
5051
$parameter = new ParameterResourceMetadataCollectionFactory(new AttributesResourceMetadataCollectionFactory(), $filterLocator);
5152
$operation = $parameter->create(WithParameter::class)->getOperation('collection');
5253
$this->assertInstanceOf(Parameters::class, $parameters = $operation->getParameters());
53-
$hydraParameter = $parameters->get('hydra');
54+
$hydraParameter = $parameters->get('hydra', QueryParameter::class);
5455
$this->assertEquals(['type' => 'foo'], $hydraParameter->getSchema());
5556
$this->assertEquals(new Parameter('test', 'query'), $hydraParameter->getOpenApi());
56-
$everywhere = $parameters->get('everywhere');
57+
$everywhere = $parameters->get('everywhere', QueryParameter::class);
5758
$this->assertEquals(new Parameter('everywhere', 'query', allowEmptyValue: true), $everywhere->getOpenApi());
5859
}
5960
}

src/State/ParameterNotFound.php

+18
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\State;
15+
16+
final class ParameterNotFound
17+
{
18+
}

src/State/Provider/ParameterProvider.php

+17-14
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414
namespace ApiPlatform\State\Provider;
1515

1616
use ApiPlatform\Metadata\Operation;
17-
use ApiPlatform\Metadata\Parameter;
18-
use ApiPlatform\Metadata\Parameters;
1917
use ApiPlatform\State\Exception\ProviderNotFoundException;
18+
use ApiPlatform\State\ParameterNotFound;
2019
use ApiPlatform\State\ParameterProviderInterface;
2120
use ApiPlatform\State\ProviderInterface;
2221
use ApiPlatform\State\Util\ParameterParserTrait;
@@ -51,20 +50,22 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5150
}
5251

5352
$context = ['operation' => $operation] + $context;
54-
$p = $operation->getParameters() ?? [];
55-
$parameters = $p instanceof Parameters ? iterator_to_array($p) : $p;
56-
foreach ($parameters as $parameter) {
57-
$key = $parameter->getKey();
58-
$values = $this->extractParameterValues($parameter, $request, $context);
59-
$key = $this->getParameterFlattenKey($key, $values);
60-
61-
if (!isset($values[$key])) {
53+
$parameters = $operation->getParameters();
54+
foreach ($parameters ?? [] as $parameter) {
55+
$values = $this->getParameterValues($parameter, $request, $context);
56+
$value = $this->extractParameterValues($parameter, $values);
57+
58+
if ((!$value || $value instanceof ParameterNotFound) && ($default = $parameter->getSchema()['default'] ?? false)) {
59+
$value = $default;
60+
}
61+
62+
if ($value instanceof ParameterNotFound) {
6263
continue;
6364
}
6465

65-
$parameters[$parameter->getKey()] = $parameter = $parameter->withExtraProperties(
66-
$parameter->getExtraProperties() + ['_api_values' => [$key => $values[$key]]]
67-
);
66+
$parameters->add($parameter->getKey(), $parameter = $parameter->withExtraProperties(
67+
$parameter->getExtraProperties() + ['_api_values' => [$parameter->getKey() => $value]]
68+
));
6869

6970
if (null === ($provider = $parameter->getProvider())) {
7071
continue;
@@ -89,7 +90,9 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
8990
}
9091
}
9192

92-
$operation = $operation->withParameters(new Parameters($parameters));
93+
if ($parameters) {
94+
$operation = $operation->withParameters($parameters);
95+
}
9396
$request?->attributes->set('_api_operation', $operation);
9497
$context['operation'] = $operation;
9598

src/State/Tests/ParameterProviderTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public function has(string $id): bool
5959
$operation = $request->attributes->get('_api_operation');
6060

6161
$this->assertEquals('ok', $operation->getName());
62-
$this->assertEquals(['order' => ['foo' => 'asc']], $operation->getParameters()->get('order')->getExtraProperties()['_api_values']);
63-
$this->assertEquals(['search' => ['a' => 'bar']], $operation->getParameters()->get('search[:property]')->getExtraProperties()['_api_values']);
62+
$this->assertEquals(['order' => ['foo' => 'asc']], $operation->getParameters()->get('order', QueryParameter::class)->getValue());
63+
$this->assertEquals(['search[:property]' => ['a' => 'bar']], $operation->getParameters()->get('search[:property]', QueryParameter::class)->getValue());
6464
}
6565

6666
public static function provide(): void

0 commit comments

Comments
 (0)