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

api-platform / core / 3712739783

pending completion
3712739783

Pull #5254

github

GitHub
Merge 9dfa88fa6 into ac711530f
Pull Request #5254: [OpenApi] Add ApiResource::openapi and deprecate openapiContext

199 of 199 new or added lines in 6 files covered. (100.0%)

7494 of 12363 relevant lines covered (60.62%)

67.55 hits per line

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

88.29
/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\Operations;
21
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
22
use ApiPlatform\Operation\PathSegmentNameGeneratorInterface;
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
    private $triggerLegacyFormatOnce = [];
33

34
    public function __construct(private readonly LinkFactoryInterface $linkFactory, private readonly PathSegmentNameGeneratorInterface $pathSegmentNameGenerator, private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null)
35
    {
36
    }
649✔
37

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

48
        foreach ($resourceMetadataCollection as $i => $resource) {
22✔
49
            /** @var ApiResource $resource */
50
            $resource = $this->configureUriVariables($resource);
20✔
51
            if ($resource->getUriTemplate()) {
20✔
52
                $resourceMetadataCollection[$i] = $resource->withExtraProperties($resource->getExtraProperties() + ['user_defined_uri_template' => true]);
6✔
53
            }
54

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

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

69
                    $operations->add($key, $operation);
9✔
70
                    continue;
9✔
71
                }
72

73
                if ($routeName = $operation->getRouteName()) {
20✔
74
                    if (!$operation->getName()) {
2✔
75
                        $operation = $operation->withName($routeName);
×
76
                    }
77

78
                    $operations->add($routeName, $operation);
2✔
79
                    continue;
2✔
80
                }
81

82
                $operation = $operation->withUriTemplate($this->generateUriTemplate($operation));
20✔
83

84
                if (!$operation->getName()) {
20✔
85
                    $operation = $operation->withName($this->getDefaultOperationName($operation, $resourceClass));
20✔
86
                }
87

88
                $operations->add($operation->getName(), $operation);
20✔
89
            }
90

91
            $resource = $resource->withOperations($operations->sort());
20✔
92
            $resourceMetadataCollection[$i] = $resource;
20✔
93
        }
94

95
        return $resourceMetadataCollection;
22✔
96
    }
97

98
    private function generateUriTemplate(HttpOperation $operation): string
99
    {
100
        $uriTemplate = $operation->getUriTemplate() ?? sprintf('/%s', $this->pathSegmentNameGenerator->getSegmentName($operation->getShortName()));
20✔
101
        $uriVariables = $operation->getUriVariables() ?? [];
20✔
102
        $legacyFormat = null;
20✔
103

104
        if (str_ends_with($uriTemplate, '{._format}') || ($legacyFormat = str_ends_with($uriTemplate, '.{_format}'))) {
20✔
105
            $uriTemplate = substr($uriTemplate, 0, -10);
×
106
        }
107

108
        if ($legacyFormat && ($this->triggerLegacyFormatOnce[$operation->getClass()] ?? true)) {
20✔
109
            $this->triggerLegacyFormatOnce[$operation->getClass()] = false;
×
110
            trigger_deprecation('api-platform/core', '3.0', sprintf('The special Symfony parameter ".{_format}" in your URI Template is deprecated, use an RFC6570 variable "{._format}" on the class "%s" instead. We will only use the RFC6570 compatible variable in 4.0.', $operation->getClass()));
×
111
        }
112

113
        if ($parameters = array_keys($uriVariables)) {
20✔
114
            foreach ($parameters as $parameterName) {
20✔
115
                $part = sprintf('/{%s}', $parameterName);
20✔
116
                if (false === strpos($uriTemplate, $part)) {
20✔
117
                    $uriTemplate .= sprintf('/{%s}', $parameterName);
20✔
118
                }
119
            }
120
        }
121

122
        return sprintf('%s%s', $uriTemplate, $legacyFormat ? '.{_format}' : '{._format}');
20✔
123
    }
124

125
    private function configureUriVariables(ApiResource|HttpOperation $operation): ApiResource|HttpOperation
126
    {
127
        // We will generate the collection route, don't initialize variables here
128
        if ($operation instanceof HttpOperation && (
20✔
129
            [] === $operation->getUriVariables() ||
20✔
130
            (
20✔
131
                $operation instanceof CollectionOperationInterface
20✔
132
                && null === $operation->getUriTemplate()
20✔
133
            )
20✔
134
        )) {
135
            if (null === $operation->getUriVariables()) {
20✔
136
                return $operation;
20✔
137
            }
138

139
            return $this->normalizeUriVariables($operation);
1✔
140
        }
141

142
        $hasUserConfiguredUriVariables = !($operation->getExtraProperties['is_legacy_resource_metadata'] ?? false);
20✔
143
        if (!$operation->getUriVariables()) {
20✔
144
            $hasUserConfiguredUriVariables = false;
20✔
145
            $operation = $operation->withUriVariables($this->transformLinksToUriVariables($this->linkFactory->createLinksFromIdentifiers($operation)));
20✔
146
        }
147

148
        $operation = $this->normalizeUriVariables($operation);
20✔
149

150
        if (!($uriTemplate = $operation->getUriTemplate())) {
20✔
151
            if ($operation instanceof HttpOperation && HttpOperation::METHOD_POST === $operation->getMethod()) {
20✔
152
                return $operation->withUriVariables([]);
20✔
153
            }
154

155
            return $operation;
20✔
156
        }
157

158
        foreach ($uriVariables = $operation->getUriVariables() as $parameterName => $link) {
9✔
159
            $uriVariables[$parameterName] = $this->linkFactory->completeLink($link);
9✔
160
        }
161
        $operation = $operation->withUriVariables($uriVariables);
9✔
162

163
        if (str_ends_with($uriTemplate, '{._format}') || str_ends_with($uriTemplate, '.{_format}')) {
9✔
164
            $uriTemplate = substr($uriTemplate, 0, -10);
7✔
165
        }
166

167
        $route = (new Route($uriTemplate))->compile();
9✔
168
        $variables = $route->getPathVariables();
9✔
169

170
        if (\count($variables) !== \count($uriVariables)) {
9✔
171
            if ($hasUserConfiguredUriVariables) {
7✔
172
                return $operation;
5✔
173
            }
174

175
            $newUriVariables = [];
3✔
176
            foreach ($variables as $variable) {
3✔
177
                if (isset($uriVariables[$variable])) {
1✔
178
                    $newUriVariables[$variable] = $uriVariables[$variable];
×
179
                    continue;
×
180
                }
181

182
                $newUriVariables[$variable] = (new Link())->withFromClass($operation->getClass())->withIdentifiers(['id'])->withParameterName($variable);
1✔
183
            }
184

185
            return $operation->withUriVariables($newUriVariables);
3✔
186
        }
187

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

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

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

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

205
        return $operation->withUriVariables($uriVariables);
×
206
    }
207

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

212
        $normalizedUriVariables = [];
20✔
213
        $resourceClass = $operation->getClass();
20✔
214

215
        foreach ($uriVariables as $parameterName => $uriVariable) {
20✔
216
            $normalizedParameterName = $parameterName;
20✔
217
            $normalizedUriVariable = $uriVariable;
20✔
218

219
            if (\is_int($normalizedParameterName)) {
20✔
220
                $normalizedParameterName = $normalizedUriVariable;
1✔
221
            }
222
            if (\is_string($normalizedUriVariable)) {
20✔
223
                $normalizedUriVariable = (new Link())->withIdentifiers([$normalizedUriVariable])->withFromClass($resourceClass);
1✔
224
            }
225
            if (\is_array($normalizedUriVariable)) {
20✔
226
                if (!isset($normalizedUriVariable['from_class']) && !isset($normalizedUriVariable['expanded_value'])) {
1✔
227
                    if (2 !== \count($normalizedUriVariable)) {
×
228
                        throw new \LogicException("The uriVariables shortcut syntax needs to be the tuple: 'uriVariable' => [fromClass, fromProperty]");
×
229
                    }
230
                    $normalizedUriVariable = (new Link())->withFromProperty($normalizedUriVariable[1])->withFromClass($normalizedUriVariable[0]);
×
231
                } else {
232
                    $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);
1✔
233
                }
234
            }
235

236
            $normalizedUriVariable = $normalizedUriVariable->withParameterName($normalizedParameterName);
20✔
237
            $normalizedUriVariables[$normalizedParameterName] = $normalizedUriVariable;
20✔
238
        }
239

240
        return $operation->withUriVariables($normalizedUriVariables);
20✔
241
    }
242

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

252
        foreach ($links as $link) {
20✔
253
            $uriVariables[$link->getParameterName()] = $link;
20✔
254
        }
255

256
        return $uriVariables;
20✔
257
    }
258
}
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