• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 19799301771

30 Nov 2025 01:04PM UTC coverage: 25.229% (-0.03%) from 25.257%
19799301771

push

github

web-flow
fix(metadata): repeatable attribute mutators (#7542)

14557 of 57700 relevant lines covered (25.23%)

28.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.92
/src/State/Provider/ParameterProvider.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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\Provider;
15

16
use ApiPlatform\Metadata\HttpOperation;
17
use ApiPlatform\Metadata\Operation;
18
use ApiPlatform\Metadata\Parameter;
19
use ApiPlatform\State\Exception\ParameterNotSupportedException;
20
use ApiPlatform\State\Exception\ProviderNotFoundException;
21
use ApiPlatform\State\ParameterNotFound;
22
use ApiPlatform\State\ParameterProvider\ReadLinkParameterProvider;
23
use ApiPlatform\State\ProviderInterface;
24
use ApiPlatform\State\StopwatchAwareInterface;
25
use ApiPlatform\State\StopwatchAwareTrait;
26
use ApiPlatform\State\Util\ParameterParserTrait;
27
use ApiPlatform\State\Util\RequestParser;
28
use Psr\Container\ContainerInterface;
29

30
/**
31
 * Loops over parameters to:
32
 *   - compute its values set as extra properties from the Parameter object (`_api_values`)
33
 *   - call the Parameter::provider if any and updates the Operation
34
 */
35
final class ParameterProvider implements ProviderInterface, StopwatchAwareInterface
36
{
37
    use ParameterParserTrait;
38
    use StopwatchAwareTrait;
39

40
    public function __construct(private readonly ?ProviderInterface $decorated = null, private readonly ?ContainerInterface $locator = null)
41
    {
42
    }
724✔
43

44
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
45
    {
46
        $this->stopwatch?->start('api_platform.provider.parameter');
691✔
47
        $request = $context['request'] ?? null;
691✔
48
        if ($request && null === $request->attributes->get('_api_query_parameters')) {
691✔
49
            $queryString = RequestParser::getQueryString($request);
665✔
50
            $request->attributes->set('_api_query_parameters', $queryString ? RequestParser::parseRequestParams($queryString) : []);
665✔
51
        }
52

53
        if ($request && null === $request->attributes->get('_api_header_parameters')) {
691✔
54
            $request->attributes->set('_api_header_parameters', $request->headers->all());
665✔
55
        }
56

57
        $parameters = $operation->getParameters();
691✔
58

59
        if ($operation instanceof HttpOperation && true === $operation->getStrictQueryParameterValidation()) {
691✔
60
            $keys = [];
4✔
61
            foreach ($parameters as $parameter) {
4✔
62
                $keys[] = $parameter->getKey();
4✔
63
            }
64

65
            foreach (array_keys($request->attributes->get('_api_query_parameters')) as $key) {
4✔
66
                if (!\in_array($key, $keys, true)) {
4✔
67
                    throw new ParameterNotSupportedException($key);
2✔
68
                }
69
            }
70
        }
71

72
        $context = ['operation' => $operation] + $context;
691✔
73

74
        foreach ($parameters ?? [] as $parameter) {
691✔
75
            $values = $this->getParameterValues($parameter, $request, $context);
416✔
76
            $value = $this->extractParameterValues($parameter, $values);
416✔
77
            // we force API Platform's value extraction, use _api_query_parameters or _api_header_parameters if you need to set a value
78
            if (isset($parameter->getExtraProperties()['_api_values'])) {
416✔
79
                unset($parameter->getExtraProperties()['_api_values']);
20✔
80
            }
81

82
            if (null !== ($default = $parameter->getSchema()['default'] ?? null) && $value instanceof ParameterNotFound) {
416✔
83
                $value = $default;
16✔
84
            }
85

86
            $parameter->setValue($value);
416✔
87
            $context['operation'] = $operation = $this->callParameterProvider($operation, $parameter, $values, $context);
416✔
88
        }
89

90
        if ($parameters) {
691✔
91
            $operation = $operation->withParameters($parameters);
412✔
92
        }
93

94
        if ($operation instanceof HttpOperation) {
691✔
95
            $operation = $this->handlePathParameters($operation, $uriVariables, $context);
665✔
96
        }
97

98
        $request?->attributes->set('_api_operation', $operation);
691✔
99
        $context['operation'] = $operation;
691✔
100
        $this->stopwatch?->stop('api_platform.provider.parameter');
691✔
101

102
        return $this->decorated?->provide($operation, $uriVariables, $context);
691✔
103
    }
104

105
    /**
106
     * TODO: uriVariables could be a Parameters instance, it'd make things easier.
107
     *
108
     * @param array<string, mixed> $uriVariables
109
     * @param array<string, mixed> $context
110
     */
111
    private function handlePathParameters(HttpOperation $operation, array $uriVariables, array $context): HttpOperation
112
    {
113
        foreach ($operation->getUriVariables() ?? [] as $key => $uriVariable) {
665✔
114
            $uriVariable = $uriVariable->withKey($key);
244✔
115
            if ($uriVariable->getSecurity() && !$uriVariable->getProvider()) {
244✔
116
                $uriVariable = $uriVariable->withProvider(ReadLinkParameterProvider::class);
4✔
117
            }
118

119
            $values = $uriVariables;
244✔
120

121
            if (!\array_key_exists($key, $uriVariables)) {
244✔
122
                continue;
108✔
123
            }
124

125
            $value = $uriVariables[$key];
146✔
126
            // we force API Platform's value extraction, use _api_query_parameters or _api_header_parameters if you need to set a value
127
            if (isset($uriVariable->getExtraProperties()['_api_values'])) {
146✔
128
                unset($uriVariable->getExtraProperties()['_api_values']);
2✔
129
            }
130

131
            if (($default = $uriVariable->getSchema()['default'] ?? false) && ($value instanceof ParameterNotFound || !$value)) {
146✔
132
                $value = $default;
×
133
            }
134

135
            $uriVariable->setValue($value);
146✔
136
            if (($op = $this->callParameterProvider($operation, $uriVariable, $values, $context)) instanceof HttpOperation) {
146✔
137
                $context['operation'] = $operation = $op;
146✔
138
            }
139
        }
140

141
        return $operation;
665✔
142
    }
143

144
    /**
145
     * @param array<string,mixed> $context
146
     */
147
    private function callParameterProvider(Operation $operation, Parameter $parameter, mixed $values, array $context): Operation
148
    {
149
        if ($parameter->getValue() instanceof ParameterNotFound) {
546✔
150
            return $operation;
396✔
151
        }
152

153
        if (null === ($provider = $parameter->getProvider())) {
524✔
154
            return $operation;
496✔
155
        }
156

157
        if (\is_callable($provider)) {
40✔
158
            if (($op = $provider($parameter, $values, $context)) instanceof Operation) {
8✔
159
                $operation = $op;
6✔
160
            }
161

162
            return $operation;
6✔
163
        }
164

165
        if (\is_string($provider)) {
34✔
166
            if (!$this->locator->has($provider)) {
34✔
167
                throw new ProviderNotFoundException(\sprintf('Provider "%s" not found on operation "%s"', $provider, $operation->getName()));
×
168
            }
169

170
            $provider = $this->locator->get($provider);
34✔
171
        }
172

173
        if (($op = $provider->provide($parameter, $values, $context)) instanceof Operation) {
34✔
174
            $operation = $op;
32✔
175
        }
176

177
        return $operation;
32✔
178
    }
179
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc