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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

0 of 2 new or added lines in 1 file covered. (0.0%)

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

89.38
/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.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\Metadata\Resource\Factory;
15

16
use ApiPlatform\Metadata\ApiResource;
17
use ApiPlatform\Metadata\CollectionOperationInterface;
18
use ApiPlatform\Metadata\HttpOperation;
19
use ApiPlatform\Metadata\Link;
20
use ApiPlatform\Metadata\Operation\PathSegmentNameGeneratorInterface;
21
use ApiPlatform\Metadata\Operations;
22
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
23
use Symfony\Component\Routing\Route;
24

25
/**
26
 * @author Antoine Bluchet <soyuka@gmail.com>
27
 */
28
final class UriTemplateResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
29
{
30
    use OperationDefaultsTrait;
31

32
    public function __construct(private readonly LinkFactoryInterface $linkFactory, private readonly PathSegmentNameGeneratorInterface $pathSegmentNameGenerator, private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null)
33
    {
UNCOV
34
    }
978✔
35

36
    /**
37
     * {@inheritdoc}
38
     */
39
    public function create(string $resourceClass): ResourceMetadataCollection
40
    {
UNCOV
41
        $resourceMetadataCollection = new ResourceMetadataCollection($resourceClass);
69✔
UNCOV
42
        if ($this->decorated) {
69✔
UNCOV
43
            $resourceMetadataCollection = $this->decorated->create($resourceClass);
69✔
44
        }
45

UNCOV
46
        foreach ($resourceMetadataCollection as $i => $resource) {
69✔
47
            /** @var ApiResource $resource */
UNCOV
48
            $resource = $this->configureUriVariables($resource);
65✔
UNCOV
49
            if ($resource->getUriTemplate()) {
65✔
UNCOV
50
                $resourceMetadataCollection[$i] = $resource->withExtraProperties($resource->getExtraProperties() + ['user_defined_uri_template' => true]);
14✔
51
            }
52

UNCOV
53
            $operations = new Operations();
65✔
UNCOV
54
            foreach ($resource->getOperations() ?? new Operations() as $key => $operation) {
65✔
55
                /** @var HttpOperation */
UNCOV
56
                $operation = $this->configureUriVariables($operation);
65✔
57

58
                if (
UNCOV
59
                    $operation->getUriTemplate()
65✔
UNCOV
60
                    && !($operation->getExtraProperties()['generated_operation'] ?? false)
65✔
61
                ) {
UNCOV
62
                    $operation = $operation->withExtraProperties($operation->getExtraProperties() + ['user_defined_uri_template' => true]);
31✔
UNCOV
63
                    if (!$operation->getName()) {
31✔
UNCOV
64
                        $operation = $operation->withName($key);
28✔
65
                    }
66

UNCOV
67
                    $operations->add($key, $operation);
31✔
UNCOV
68
                    continue;
31✔
69
                }
70

UNCOV
71
                if ($routeName = $operation->getRouteName()) {
58✔
72
                    if (!$operation->getName()) {
1✔
73
                        $operation = $operation->withName($routeName);
×
74
                    }
75

76
                    $operations->add($operation->getName(), $operation);
1✔
77
                    continue;
1✔
78
                }
79

UNCOV
80
                $operation = $operation->withUriTemplate($this->generateUriTemplate($operation));
58✔
81

UNCOV
82
                if (!$operation->getName()) {
58✔
UNCOV
83
                    $operation = $operation->withName($this->getDefaultOperationName($operation, $resourceClass));
58✔
84
                }
85

UNCOV
86
                $operations->add($operation->getName(), $operation);
58✔
87
            }
88

UNCOV
89
            $resource = $resource->withOperations($operations);
65✔
UNCOV
90
            $resourceMetadataCollection[$i] = $resource;
65✔
91
        }
92

UNCOV
93
        return $resourceMetadataCollection;
69✔
94
    }
95

96
    private function generateUriTemplate(HttpOperation $operation): string
97
    {
UNCOV
98
        $uriTemplate = $operation->getUriTemplate() ?? \sprintf('/%s', $this->pathSegmentNameGenerator->getSegmentName($operation->getShortName()));
58✔
UNCOV
99
        $uriVariables = $operation->getUriVariables() ?? [];
58✔
100

UNCOV
101
        if (str_ends_with($uriTemplate, '{._format}')) {
58✔
102
            $uriTemplate = substr($uriTemplate, 0, -10);
×
103
        }
104

UNCOV
105
        if ($parameters = array_keys($uriVariables)) {
58✔
UNCOV
106
            foreach ($parameters as $parameterName) {
56✔
UNCOV
107
                $part = \sprintf('/{%s}', $parameterName);
56✔
UNCOV
108
                if (!str_contains($uriTemplate, $part)) {
56✔
UNCOV
109
                    $uriTemplate .= \sprintf('/{%s}', $parameterName);
51✔
110
                }
111
            }
112
        }
113

UNCOV
114
        return \sprintf('%s%s', $uriTemplate, '{._format}');
58✔
115
    }
116

117
    private function configureUriVariables(ApiResource|HttpOperation $operation): ApiResource|HttpOperation
118
    {
119
        // We will generate the collection route, don't initialize variables here
UNCOV
120
        if ($operation instanceof HttpOperation && (
65✔
UNCOV
121
            [] === $operation->getUriVariables()
65✔
UNCOV
122
            || (
65✔
UNCOV
123
                $operation instanceof CollectionOperationInterface
65✔
UNCOV
124
                && null === $operation->getUriTemplate()
65✔
UNCOV
125
            )
65✔
126
        )) {
UNCOV
127
            if (null === $operation->getUriVariables()) {
44✔
UNCOV
128
                return $operation;
44✔
129
            }
130

131
            return $this->normalizeUriVariables($operation);
1✔
132
        }
133

UNCOV
134
        $hasUserConfiguredUriVariables = !($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false);
65✔
UNCOV
135
        if (!$operation->getUriVariables()) {
65✔
UNCOV
136
            $hasUserConfiguredUriVariables = false;
63✔
UNCOV
137
            $operation = $operation->withUriVariables($this->transformLinksToUriVariables($this->linkFactory->createLinksFromIdentifiers($operation)));
63✔
138
        }
139

UNCOV
140
        $operation = $this->normalizeUriVariables($operation);
65✔
141

UNCOV
142
        if (!($uriTemplate = $operation->getUriTemplate())) {
65✔
UNCOV
143
            if ($operation instanceof HttpOperation && 'POST' === $operation->getMethod()) {
63✔
UNCOV
144
                return $operation->withUriVariables([]);
32✔
145
            }
146

UNCOV
147
            return $operation;
63✔
148
        }
149

UNCOV
150
        foreach ($uriVariables = $operation->getUriVariables() as $parameterName => $l) {
33✔
UNCOV
151
            $link = null === $l->getFromClass() ? $l->withFromClass($operation->getClass()) : $l;
27✔
UNCOV
152
            $uriVariables[$parameterName] = $this->linkFactory->completeLink($link);
27✔
153
        }
UNCOV
154
        $operation = $operation->withUriVariables($uriVariables);
33✔
155

UNCOV
156
        if (str_ends_with($uriTemplate, '{._format}') || str_ends_with($uriTemplate, '.{_format}')) {
33✔
UNCOV
157
            $uriTemplate = substr($uriTemplate, 0, -10);
16✔
158
        }
159

160
        // TODO: move this to the Symfony bridge
UNCOV
161
        if (class_exists(Route::class)) {
33✔
UNCOV
162
            $route = (new Route($uriTemplate))->compile();
33✔
UNCOV
163
            $variables = $route->getPathVariables();
33✔
164

UNCOV
165
            if (\count($variables) !== \count($uriVariables)) {
33✔
UNCOV
166
                if ($hasUserConfiguredUriVariables) {
23✔
UNCOV
167
                    return $operation;
7✔
168
                }
169

UNCOV
170
                $newUriVariables = [];
19✔
UNCOV
171
                foreach ($variables as $variable) {
19✔
UNCOV
172
                    if (isset($uriVariables[$variable])) {
6✔
173
                        $newUriVariables[$variable] = $uriVariables[$variable];
×
174
                        continue;
×
175
                    }
176

UNCOV
177
                    $newUriVariables[$variable] = (new Link())
6✔
UNCOV
178
                        ->withFromClass($operation->getClass())
6✔
UNCOV
179
                        ->withIdentifiers([property_exists($operation->getClass(), $variable) ? $variable : 'id'])
6✔
UNCOV
180
                        ->withParameterName($variable);
6✔
181
                }
182

UNCOV
183
                return $operation->withUriVariables($newUriVariables);
19✔
184
            }
185

186
            // When an operation is generated we need to find properties matching it's uri variables
UNCOV
187
            if (!($operation->getExtraProperties()['generated_operation'] ?? false) || !$this->linkFactory instanceof PropertyLinkFactoryInterface) {
25✔
UNCOV
188
                return $operation;
25✔
189
            }
190

191
            $diff = array_diff($variables, array_keys($uriVariables));
1✔
192
            if (0 === \count($diff)) {
1✔
193
                return $operation;
1✔
194
            }
195

196
            // We generated this operation but there're some missing identifiers
197
            $uriVariables = 'POST' === $operation->getMethod() || $operation instanceof CollectionOperationInterface ? [] : $operation->getUriVariables();
×
198

199
            foreach ($diff as $key) {
×
200
                $uriVariables[$key] = $this->linkFactory->createLinkFromProperty($operation, $key);
×
201
            }
202

203
            return $operation->withUriVariables($uriVariables);
×
204
        }
205

206
        return $operation;
×
207
    }
208

209
    private function normalizeUriVariables(ApiResource|HttpOperation $operation): ApiResource|HttpOperation
210
    {
UNCOV
211
        $uriVariables = (array) ($operation->getUriVariables() ?? []);
65✔
212

UNCOV
213
        $normalizedUriVariables = [];
65✔
UNCOV
214
        $resourceClass = $operation->getClass();
65✔
215

UNCOV
216
        foreach ($uriVariables as $parameterName => $uriVariable) {
65✔
UNCOV
217
            $normalizedParameterName = $parameterName;
59✔
UNCOV
218
            $normalizedUriVariable = $uriVariable;
59✔
219

UNCOV
220
            if (\is_int($normalizedParameterName)) {
59✔
UNCOV
221
                $normalizedParameterName = $normalizedUriVariable;
6✔
222
            }
UNCOV
223
            if (\is_string($normalizedUriVariable)) {
59✔
UNCOV
224
                $normalizedUriVariable = (new Link())->withIdentifiers([$normalizedUriVariable])->withFromClass($resourceClass);
6✔
225
            }
UNCOV
226
            if (\is_array($normalizedUriVariable)) {
59✔
227
                if (!isset($normalizedUriVariable['from_class']) && !isset($normalizedUriVariable['expanded_value'])) {
1✔
228
                    if (2 !== \count($normalizedUriVariable)) {
×
229
                        throw new \LogicException("The uriVariables shortcut syntax needs to be the tuple: 'uriVariable' => [fromClass, fromProperty]");
×
230
                    }
231
                    $normalizedUriVariable = (new Link())->withFromProperty($normalizedUriVariable[1])->withFromClass($normalizedUriVariable[0]);
×
232
                } else {
233
                    $normalizedUriVariable = new Link($normalizedParameterName, $normalizedUriVariable['from_property'] ?? null, $normalizedUriVariable['to_property'] ?? null, $normalizedUriVariable['from_class'] ?? null, $normalizedUriVariable['to_class'] ?? null, $normalizedUriVariable['identifiers'] ?? null, $normalizedUriVariable['composite_identifier'] ?? null, $normalizedUriVariable['expanded_value'] ?? null, $normalizedUriVariable['security'] ?? null);
1✔
234
                }
235
            }
236

UNCOV
237
            $normalizedUriVariable = $normalizedUriVariable->withParameterName($normalizedParameterName);
59✔
UNCOV
238
            $normalizedUriVariables[$normalizedParameterName] = $normalizedUriVariable;
59✔
239
        }
240

UNCOV
241
        return $operation->withUriVariables($normalizedUriVariables);
65✔
242
    }
243

244
    /**
245
     * @param Link[] $links
246
     *
247
     * @return array<string, Link>
248
     */
249
    private function transformLinksToUriVariables(array $links): array
250
    {
UNCOV
251
        $uriVariables = [];
63✔
252

UNCOV
253
        foreach ($links as $link) {
63✔
UNCOV
254
            $uriVariables[$link->getParameterName()] = $link;
55✔
255
        }
256

UNCOV
257
        return $uriVariables;
63✔
258
    }
259
}
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

© 2025 Coveralls, Inc