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\NelmioApiDoc\Extractor\AnnotationsProvider; |
15
|
|
|
|
16
|
|
|
use ApiPlatform\Core\Api\FilterCollection; |
17
|
|
|
use ApiPlatform\Core\Api\FilterLocatorTrait; |
18
|
|
|
use ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser; |
19
|
|
|
use ApiPlatform\Core\Bridge\Symfony\Routing\OperationMethodResolverInterface; |
20
|
|
|
use ApiPlatform\Core\Documentation\Documentation; |
21
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
22
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; |
23
|
|
|
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; |
24
|
|
|
use Nelmio\ApiDocBundle\Annotation\ApiDoc; |
25
|
|
|
use Nelmio\ApiDocBundle\Extractor\AnnotationsProviderInterface; |
26
|
|
|
use Psr\Container\ContainerInterface; |
27
|
|
|
use Symfony\Component\HttpFoundation\Request; |
28
|
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Creates Nelmio ApiDoc annotations for the api platform. |
32
|
|
|
* |
33
|
|
|
* @author Kévin Dunglas <[email protected]> |
34
|
|
|
* @author Teoh Han Hui <[email protected]> |
35
|
|
|
* |
36
|
|
|
* @deprecated since version 2.2, to be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. |
37
|
|
|
*/ |
38
|
|
|
final class ApiPlatformProvider implements AnnotationsProviderInterface |
39
|
|
|
{ |
40
|
|
|
use FilterLocatorTrait; |
41
|
|
|
|
42
|
|
|
private $resourceNameCollectionFactory; |
43
|
|
|
private $documentationNormalizer; |
44
|
|
|
private $resourceMetadataFactory; |
45
|
|
|
private $operationMethodResolver; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection |
49
|
|
|
*/ |
50
|
|
|
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, NormalizerInterface $documentationNormalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator, OperationMethodResolverInterface $operationMethodResolver) |
51
|
|
|
{ |
52
|
|
|
@trigger_error('The '.__CLASS__.' class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.', E_USER_DEPRECATED); |
53
|
|
|
|
54
|
|
|
$this->setFilterLocator($filterLocator); |
55
|
|
|
|
56
|
|
|
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory; |
57
|
|
|
$this->documentationNormalizer = $documentationNormalizer; |
58
|
|
|
$this->resourceMetadataFactory = $resourceMetadataFactory; |
59
|
|
|
$this->operationMethodResolver = $operationMethodResolver; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* {@inheritdoc} |
64
|
|
|
*/ |
65
|
|
|
public function getAnnotations(): array |
66
|
|
|
{ |
67
|
|
|
$resourceNameCollection = $this->resourceNameCollectionFactory->create(); |
68
|
|
|
$hydraDoc = $this->documentationNormalizer->normalize(new Documentation($resourceNameCollection)); |
69
|
|
|
if (empty($hydraDoc)) { |
70
|
|
|
return []; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
$entrypointHydraDoc = $this->getResourceHydraDoc($hydraDoc, '#Entrypoint'); |
74
|
|
|
if (null === $entrypointHydraDoc) { |
75
|
|
|
return []; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
$annotations = []; |
79
|
|
|
foreach ($resourceNameCollection as $resourceClass) { |
80
|
|
|
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
81
|
|
|
|
82
|
|
|
$prefixedShortName = ($iri = $resourceMetadata->getIri()) ? $iri : '#'.$resourceMetadata->getShortName(); |
83
|
|
|
$resourceHydraDoc = $this->getResourceHydraDoc($hydraDoc, $prefixedShortName); |
84
|
|
|
if (null === $resourceHydraDoc) { |
85
|
|
|
continue; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
View Code Duplication |
if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { |
|
|
|
|
89
|
|
|
foreach ($collectionOperations as $operationName => $operation) { |
90
|
|
|
$annotations[] = $this->getApiDoc(true, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc, $entrypointHydraDoc); |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { |
95
|
|
|
foreach ($itemOperations as $operationName => $operation) { |
96
|
|
|
$annotations[] = $this->getApiDoc(false, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc); |
97
|
|
|
} |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
return $annotations; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Builds ApiDoc annotation from ApiPlatform data. |
106
|
|
|
* |
107
|
|
|
* @param bool $collection |
108
|
|
|
* @param string $resourceClass |
109
|
|
|
* @param ResourceMetadata $resourceMetadata |
110
|
|
|
* @param string $operationName |
111
|
|
|
* @param array $resourceHydraDoc |
112
|
|
|
* @param array $entrypointHydraDoc |
113
|
|
|
* |
114
|
|
|
* @return ApiDoc |
115
|
|
|
*/ |
116
|
|
|
private function getApiDoc(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $resourceHydraDoc, array $entrypointHydraDoc = []): ApiDoc |
117
|
|
|
{ |
118
|
|
|
if ($collection) { |
119
|
|
|
$method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); |
120
|
|
|
$route = $this->operationMethodResolver->getCollectionOperationRoute($resourceClass, $operationName); |
121
|
|
|
$operationHydraDoc = $this->getCollectionOperationHydraDoc($resourceMetadata->getShortName(), $method, $entrypointHydraDoc); |
122
|
|
|
} else { |
123
|
|
|
$method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); |
124
|
|
|
$route = $this->operationMethodResolver->getItemOperationRoute($resourceClass, $operationName); |
125
|
|
|
$operationHydraDoc = $this->getOperationHydraDoc($method, $resourceHydraDoc); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
$data = [ |
129
|
|
|
'resource' => $route->getPath(), |
130
|
|
|
'description' => $operationHydraDoc['hydra:title'] ?? '', |
131
|
|
|
'resourceDescription' => $resourceHydraDoc['hydra:title'] ?? '', |
132
|
|
|
'section' => $resourceHydraDoc['hydra:title'] ?? '', |
133
|
|
|
]; |
134
|
|
|
|
135
|
|
View Code Duplication |
if (isset($operationHydraDoc['expects']) && 'owl:Nothing' !== $operationHydraDoc['expects']) { |
|
|
|
|
136
|
|
|
$data['input'] = sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass, $operationName); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
View Code Duplication |
if (isset($operationHydraDoc['returns']) && 'owl:Nothing' !== $operationHydraDoc['returns']) { |
|
|
|
|
140
|
|
|
$data['output'] = sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass, $operationName); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
if ($collection && Request::METHOD_GET === $method) { |
144
|
|
|
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); |
145
|
|
|
|
146
|
|
|
$data['filters'] = []; |
147
|
|
|
foreach ($resourceFilters as $filterId) { |
148
|
|
|
if ($filter = $this->getFilter($filterId)) { |
|
|
|
|
149
|
|
|
foreach ($filter->getDescription($resourceClass) as $name => $definition) { |
150
|
|
|
$data['filters'][] = ['name' => $name] + $definition; |
151
|
|
|
} |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
$apiDoc = new ApiDoc($data); |
157
|
|
|
$apiDoc->setRoute($route); |
158
|
|
|
|
159
|
|
|
return $apiDoc; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Gets Hydra documentation for the given resource. |
164
|
|
|
* |
165
|
|
|
* @param array $hydraApiDoc |
166
|
|
|
* @param string $prefixedShortName |
167
|
|
|
* |
168
|
|
|
* @return array|null |
169
|
|
|
*/ |
170
|
|
|
private function getResourceHydraDoc(array $hydraApiDoc, string $prefixedShortName) |
171
|
|
|
{ |
172
|
|
|
if (!isset($hydraApiDoc['hydra:supportedClass']) || !is_array($hydraApiDoc['hydra:supportedClass'])) { |
173
|
|
|
return null; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
foreach ($hydraApiDoc['hydra:supportedClass'] as $supportedClass) { |
177
|
|
|
if (isset($supportedClass['@id']) && $supportedClass['@id'] === $prefixedShortName) { |
178
|
|
|
return $supportedClass; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
return null; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Gets the Hydra documentation of a given operation. |
187
|
|
|
* |
188
|
|
|
* @param string $method |
189
|
|
|
* @param array $hydraDoc |
190
|
|
|
* |
191
|
|
|
* @return array |
192
|
|
|
*/ |
193
|
|
|
private function getOperationHydraDoc(string $method, array $hydraDoc): array |
194
|
|
|
{ |
195
|
|
|
if (!isset($hydraDoc['hydra:supportedOperation']) || !is_array($hydraDoc['hydra:supportedOperation'])) { |
196
|
|
|
return []; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
foreach ($hydraDoc['hydra:supportedOperation'] as $supportedOperation) { |
200
|
|
|
if ($supportedOperation['hydra:method'] === $method) { |
201
|
|
|
return $supportedOperation; |
202
|
|
|
} |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
return []; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Gets the Hydra documentation for the collection operation. |
210
|
|
|
* |
211
|
|
|
* @param string $shortName |
212
|
|
|
* @param string $method |
213
|
|
|
* @param array $hydraEntrypointDoc |
214
|
|
|
* |
215
|
|
|
* @return array |
216
|
|
|
*/ |
217
|
|
|
private function getCollectionOperationHydraDoc(string $shortName, string $method, array $hydraEntrypointDoc): array |
218
|
|
|
{ |
219
|
|
|
if (!isset($hydraEntrypointDoc['hydra:supportedProperty']) || !is_array($hydraEntrypointDoc['hydra:supportedProperty'])) { |
220
|
|
|
return []; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$propertyName = '#Entrypoint/'.lcfirst($shortName); |
224
|
|
|
|
225
|
|
|
foreach ($hydraEntrypointDoc['hydra:supportedProperty'] as $supportedProperty) { |
226
|
|
|
if (isset($supportedProperty['hydra:property']['@id']) |
227
|
|
|
&& $supportedProperty['hydra:property']['@id'] === $propertyName) { |
228
|
|
|
return $this->getOperationHydraDoc($method, $supportedProperty['hydra:property']); |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
return []; |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.