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\Swagger\Serializer; |
15
|
|
|
|
16
|
|
|
use ApiPlatform\Core\Api\FilterCollection; |
17
|
|
|
use ApiPlatform\Core\Api\OperationMethodResolverInterface; |
18
|
|
|
use ApiPlatform\Core\Api\ResourceClassResolverInterface; |
19
|
|
|
use ApiPlatform\Core\Api\UrlGeneratorInterface; |
20
|
|
|
use ApiPlatform\Core\Documentation\Documentation; |
21
|
|
|
use ApiPlatform\Core\Exception\RuntimeException; |
22
|
|
|
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; |
23
|
|
|
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; |
24
|
|
|
use ApiPlatform\Core\Metadata\Property\PropertyMetadata; |
25
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
26
|
|
|
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; |
27
|
|
|
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface; |
28
|
|
|
use Symfony\Component\PropertyInfo\Type; |
29
|
|
|
use Symfony\Component\Serializer\NameConverter\NameConverterInterface; |
30
|
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Creates a machine readable Swagger API documentation. |
34
|
|
|
* |
35
|
|
|
* @author Amrouche Hamza <[email protected]> |
36
|
|
|
* @author Teoh Han Hui <[email protected]> |
37
|
|
|
* @author Kévin Dunglas <[email protected]> |
38
|
|
|
*/ |
39
|
|
|
final class DocumentationNormalizer implements NormalizerInterface |
40
|
|
|
{ |
41
|
|
|
const SWAGGER_VERSION = '2.0'; |
42
|
|
|
const FORMAT = 'json'; |
43
|
|
|
|
44
|
|
|
private $resourceMetadataFactory; |
45
|
|
|
private $propertyNameCollectionFactory; |
46
|
|
|
private $propertyMetadataFactory; |
47
|
|
|
private $resourceClassResolver; |
48
|
|
|
private $operationMethodResolver; |
49
|
|
|
private $operationPathResolver; |
50
|
|
|
private $urlGenerator; |
51
|
|
|
private $filterCollection; |
52
|
|
|
private $nameConverter; |
53
|
|
|
private $oauthEnabled; |
54
|
|
|
private $oauthType; |
55
|
|
|
private $oauthFlow; |
56
|
|
|
private $oauthTokenUrl; |
57
|
|
|
private $oauthAuthorizationUrl; |
58
|
|
|
private $oauthScopes; |
59
|
|
|
|
60
|
|
|
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator, FilterCollection $filterCollection = null, NameConverterInterface $nameConverter = null, $oauthEnabled = false, $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = []) |
61
|
|
|
{ |
62
|
|
|
$this->resourceMetadataFactory = $resourceMetadataFactory; |
63
|
|
|
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory; |
64
|
|
|
$this->propertyMetadataFactory = $propertyMetadataFactory; |
65
|
|
|
$this->resourceClassResolver = $resourceClassResolver; |
66
|
|
|
$this->operationMethodResolver = $operationMethodResolver; |
67
|
|
|
$this->operationPathResolver = $operationPathResolver; |
68
|
|
|
$this->urlGenerator = $urlGenerator; |
69
|
|
|
$this->filterCollection = $filterCollection; |
70
|
|
|
$this->nameConverter = $nameConverter; |
71
|
|
|
$this->oauthEnabled = $oauthEnabled; |
72
|
|
|
$this->oauthType = $oauthType; |
73
|
|
|
$this->oauthFlow = $oauthFlow; |
74
|
|
|
$this->oauthTokenUrl = $oauthTokenUrl; |
75
|
|
|
$this->oauthAuthorizationUrl = $oauthAuthorizationUrl; |
76
|
|
|
$this->oauthScopes = $oauthScopes; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* {@inheritdoc} |
81
|
|
|
*/ |
82
|
|
|
public function normalize($object, $format = null, array $context = []) |
83
|
|
|
{ |
84
|
|
|
$mimeTypes = $object->getMimeTypes(); |
85
|
|
|
$definitions = new \ArrayObject(); |
86
|
|
|
$paths = new \ArrayObject(); |
87
|
|
|
|
88
|
|
|
foreach ($object->getResourceNameCollection() as $resourceClass) { |
89
|
|
|
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
90
|
|
|
$resourceShortName = $resourceMetadata->getShortName(); |
91
|
|
|
|
92
|
|
|
$this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, true); |
93
|
|
|
$this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, false); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$definitions->ksort(); |
97
|
|
|
$paths->ksort(); |
98
|
|
|
|
99
|
|
|
return $this->computeDoc($object, $definitions, $paths); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Updates the list of entries in the paths collection. |
104
|
|
|
* |
105
|
|
|
* @param \ArrayObject $paths |
106
|
|
|
* @param \ArrayObject $definitions |
107
|
|
|
* @param string $resourceClass |
108
|
|
|
* @param string $resourceShortName |
109
|
|
|
* @param ResourceMetadata $resourceMetadata |
110
|
|
|
* @param array $mimeTypes |
111
|
|
|
* @param bool $collection |
112
|
|
|
*/ |
113
|
|
|
private function addPaths(\ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, array $mimeTypes, bool $collection) |
114
|
|
|
{ |
115
|
|
|
$operations = $collection ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations(); |
116
|
|
|
|
117
|
|
|
if (!$operations) { |
118
|
|
|
return; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
foreach ($operations as $operationName => $operation) { |
122
|
|
|
$path = $this->getPath($resourceShortName, $operation, $collection); |
123
|
|
|
$method = $collection ? $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); |
124
|
|
|
|
125
|
|
|
$paths[$path][strtolower($method)] = $this->getPathOperation($operationName, $operation, $method, $collection, $resourceClass, $resourceMetadata, $mimeTypes, $definitions); |
126
|
|
|
} |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Gets the path for an operation. |
131
|
|
|
* |
132
|
|
|
* If the path ends with the optional _format parameter, it is removed |
133
|
|
|
* as optional path parameters are not yet supported. |
134
|
|
|
* |
135
|
|
|
* @see https://github.com/OAI/OpenAPI-Specification/issues/93 |
136
|
|
|
* |
137
|
|
|
* @param string $resourceShortName |
138
|
|
|
* @param array $operation |
139
|
|
|
* @param bool $collection |
140
|
|
|
* |
141
|
|
|
* @return string |
142
|
|
|
*/ |
143
|
|
|
private function getPath(string $resourceShortName, array $operation, bool $collection): string |
144
|
|
|
{ |
145
|
|
|
$path = $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $collection); |
146
|
|
|
if ('.{_format}' === substr($path, -10)) { |
147
|
|
|
$path = substr($path, 0, -10); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
return $path; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Gets a path Operation Object. |
155
|
|
|
* |
156
|
|
|
* @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object |
157
|
|
|
* |
158
|
|
|
* @param string $operationName |
159
|
|
|
* @param array $operation |
160
|
|
|
* @param string $method |
161
|
|
|
* @param bool $collection |
162
|
|
|
* @param string $resourceClass |
163
|
|
|
* @param ResourceMetadata $resourceMetadata |
164
|
|
|
* @param string[] $mimeTypes |
165
|
|
|
* @param \ArrayObject $definitions |
166
|
|
|
* |
167
|
|
|
* @return \ArrayObject |
168
|
|
|
*/ |
169
|
|
|
private function getPathOperation(string $operationName, array $operation, string $method, bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, array $mimeTypes, \ArrayObject $definitions): \ArrayObject |
170
|
|
|
{ |
171
|
|
|
$pathOperation = new \ArrayObject($operation['swagger_context'] ?? []); |
172
|
|
|
$resourceShortName = $resourceMetadata->getShortName(); |
173
|
|
|
$pathOperation['tags'] ?? $pathOperation['tags'] = [$resourceShortName]; |
174
|
|
|
$pathOperation['operationId'] ?? $pathOperation['operationId'] = lcfirst($operationName).ucfirst($resourceShortName).ucfirst($collection ? 'collection' : 'item'); |
175
|
|
|
|
176
|
|
|
switch ($method) { |
177
|
|
|
case 'GET': |
178
|
|
|
return $this->updateGetOperation($pathOperation, $mimeTypes, $collection, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); |
179
|
|
|
case 'POST': |
180
|
|
|
return $this->updatePostOperation($pathOperation, $mimeTypes, $collection, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); |
181
|
|
|
case 'PUT': |
182
|
|
|
return $this->updatePutOperation($pathOperation, $mimeTypes, $collection, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); |
183
|
|
|
case 'PATCH': |
|
|
|
|
184
|
|
|
$pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Updates the %s resource.', $resourceShortName); |
185
|
|
|
case 'DELETE': |
186
|
|
|
return $this->updateDeleteOperation($pathOperation, $resourceShortName); |
187
|
|
|
default: |
|
|
|
|
188
|
|
|
throw new RuntimeException(sprintf('Method "%s" is not supported', $method)); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @param \ArrayObject $pathOperation |
194
|
|
|
* @param array $mimeTypes |
195
|
|
|
* @param bool $collection |
196
|
|
|
* @param ResourceMetadata $resourceMetadata |
197
|
|
|
* @param string $resourceClass |
198
|
|
|
* @param string $resourceShortName |
199
|
|
|
* @param string $operationName |
200
|
|
|
* @param \ArrayObject $definitions |
201
|
|
|
* |
202
|
|
|
* @return \ArrayObject |
203
|
|
|
*/ |
204
|
|
|
private function updateGetOperation(\ArrayObject $pathOperation, array $mimeTypes, bool $collection, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions) |
205
|
|
|
{ |
206
|
|
|
$serializerContext = $this->getSerializerContext($collection, false, $resourceMetadata, $operationName); |
207
|
|
|
$responseDefinitionKey = $this->getDefinition($definitions, $resourceMetadata, $resourceClass, $serializerContext); |
208
|
|
|
|
209
|
|
|
$pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; |
210
|
|
|
|
211
|
|
|
if ($collection) { |
212
|
|
|
$pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves the collection of %s resources.', $resourceShortName); |
213
|
|
|
$pathOperation['responses'] ?? $pathOperation['responses'] = [ |
214
|
|
|
'200' => [ |
215
|
|
|
'description' => sprintf('%s collection response', $resourceShortName), |
216
|
|
|
'schema' => [ |
217
|
|
|
'type' => 'array', |
218
|
|
|
'items' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)], |
219
|
|
|
], |
220
|
|
|
], |
221
|
|
|
]; |
222
|
|
|
|
223
|
|
View Code Duplication |
if (!isset($pathOperation['parameters']) && $parameters = $this->getFiltersParameters($resourceClass, $operationName, $resourceMetadata, $definitions, $serializerContext)) { |
|
|
|
|
224
|
|
|
$pathOperation['parameters'] = $parameters; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
return $pathOperation; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves a %s resource.', $resourceShortName); |
231
|
|
|
$pathOperation['parameters'] ?? $pathOperation['parameters'] = [[ |
232
|
|
|
'name' => 'id', |
233
|
|
|
'in' => 'path', |
234
|
|
|
'required' => true, |
235
|
|
|
'type' => 'integer', |
236
|
|
|
]]; |
237
|
|
|
$pathOperation['responses'] ?? $pathOperation['responses'] = [ |
238
|
|
|
'200' => [ |
239
|
|
|
'description' => sprintf('%s resource response', $resourceShortName), |
240
|
|
|
'schema' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)], |
241
|
|
|
], |
242
|
|
|
'404' => ['description' => 'Resource not found'], |
243
|
|
|
]; |
244
|
|
|
|
245
|
|
|
return $pathOperation; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* @param \ArrayObject $pathOperation |
250
|
|
|
* @param array $mimeTypes |
251
|
|
|
* @param bool $collection |
252
|
|
|
* @param ResourceMetadata $resourceMetadata |
253
|
|
|
* @param string $resourceClass |
254
|
|
|
* @param string $resourceShortName |
255
|
|
|
* @param string $operationName |
256
|
|
|
* @param \ArrayObject $definitions |
257
|
|
|
* |
258
|
|
|
* @return \ArrayObject |
259
|
|
|
*/ |
260
|
|
|
private function updatePostOperation(\ArrayObject $pathOperation, array $mimeTypes, bool $collection, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions) |
261
|
|
|
{ |
262
|
|
|
$pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes; |
263
|
|
|
$pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; |
264
|
|
|
$pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Creates a %s resource.', $resourceShortName); |
265
|
|
|
$pathOperation['parameters'] ?? $pathOperation['parameters'] = [[ |
266
|
|
|
'name' => lcfirst($resourceShortName), |
267
|
|
|
'in' => 'body', |
268
|
|
|
'description' => sprintf('The new %s resource', $resourceShortName), |
269
|
|
|
'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
270
|
|
|
$this->getSerializerContext($collection, true, $resourceMetadata, $operationName) |
271
|
|
|
))], |
272
|
|
|
]]; |
273
|
|
|
$pathOperation['responses'] ?? $pathOperation['responses'] = [ |
274
|
|
|
'201' => [ |
275
|
|
|
'description' => sprintf('%s resource created', $resourceShortName), |
276
|
|
|
'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
277
|
|
|
$this->getSerializerContext($collection, false, $resourceMetadata, $operationName) |
278
|
|
|
))], |
279
|
|
|
], |
280
|
|
|
'400' => ['description' => 'Invalid input'], |
281
|
|
|
'404' => ['description' => 'Resource not found'], |
282
|
|
|
]; |
283
|
|
|
|
284
|
|
|
return $pathOperation; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* @param \ArrayObject $pathOperation |
289
|
|
|
* @param array $mimeTypes |
290
|
|
|
* @param bool $collection |
291
|
|
|
* @param ResourceMetadata $resourceMetadata |
292
|
|
|
* @param string $resourceClass |
293
|
|
|
* @param string $resourceShortName |
294
|
|
|
* @param string $operationName |
295
|
|
|
* @param \ArrayObject $definitions |
296
|
|
|
* |
297
|
|
|
* @return \ArrayObject |
298
|
|
|
*/ |
299
|
|
|
private function updatePutOperation(\ArrayObject $pathOperation, array $mimeTypes, bool $collection, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions) |
300
|
|
|
{ |
301
|
|
|
$pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes; |
302
|
|
|
$pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; |
303
|
|
|
$pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Replaces the %s resource.', $resourceShortName); |
304
|
|
|
$pathOperation['parameters'] ?? $pathOperation['parameters'] = [ |
305
|
|
|
[ |
306
|
|
|
'name' => 'id', |
307
|
|
|
'in' => 'path', |
308
|
|
|
'type' => 'integer', |
309
|
|
|
'required' => true, |
310
|
|
|
], |
311
|
|
|
[ |
312
|
|
|
'name' => lcfirst($resourceShortName), |
313
|
|
|
'in' => 'body', |
314
|
|
|
'description' => sprintf('The updated %s resource', $resourceShortName), |
315
|
|
|
'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
316
|
|
|
$this->getSerializerContext($collection, true, $resourceMetadata, $operationName) |
317
|
|
|
))], |
318
|
|
|
], |
319
|
|
|
]; |
320
|
|
|
$pathOperation['responses'] ?? $pathOperation['responses'] = [ |
321
|
|
|
'200' => [ |
322
|
|
|
'description' => sprintf('%s resource updated', $resourceShortName), |
323
|
|
|
'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
324
|
|
|
$this->getSerializerContext($collection, false, $resourceMetadata, $operationName) |
325
|
|
|
))], |
326
|
|
|
], |
327
|
|
|
'400' => ['description' => 'Invalid input'], |
328
|
|
|
'404' => ['description' => 'Resource not found'], |
329
|
|
|
]; |
330
|
|
|
|
331
|
|
|
return $pathOperation; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* @param \ArrayObject $pathOperation |
336
|
|
|
* @param string $resourceShortName |
337
|
|
|
* |
338
|
|
|
* @return \ArrayObject |
339
|
|
|
*/ |
340
|
|
|
private function updateDeleteOperation(\ArrayObject $pathOperation, string $resourceShortName): \ArrayObject |
341
|
|
|
{ |
342
|
|
|
$pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Removes the %s resource.', $resourceShortName); |
343
|
|
|
$pathOperation['responses'] ?? $pathOperation['responses'] = [ |
344
|
|
|
'204' => ['description' => sprintf('%s resource deleted', $resourceShortName)], |
345
|
|
|
'404' => ['description' => 'Resource not found'], |
346
|
|
|
]; |
347
|
|
|
|
348
|
|
|
$pathOperation['parameters'] ?? $pathOperation['parameters'] = [[ |
349
|
|
|
'name' => 'id', |
350
|
|
|
'in' => 'path', |
351
|
|
|
'type' => 'integer', |
352
|
|
|
'required' => true, |
353
|
|
|
]]; |
354
|
|
|
|
355
|
|
|
return $pathOperation; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* @param \ArrayObject $definitions |
360
|
|
|
* @param ResourceMetadata $resourceMetadata |
361
|
|
|
* @param string $resourceClass |
362
|
|
|
* @param array|null $serializerContext |
363
|
|
|
* |
364
|
|
|
* @return string |
365
|
|
|
*/ |
366
|
|
|
private function getDefinition(\ArrayObject $definitions, ResourceMetadata $resourceMetadata, string $resourceClass, array $serializerContext = null): string |
367
|
|
|
{ |
368
|
|
|
if (isset($serializerContext['groups'])) { |
369
|
|
|
$definitionKey = sprintf('%s_%s', $resourceMetadata->getShortName(), md5(serialize($serializerContext['groups']))); |
370
|
|
|
} else { |
371
|
|
|
$definitionKey = $resourceMetadata->getShortName(); |
372
|
|
|
} |
373
|
|
|
|
374
|
|
View Code Duplication |
if (!isset($definitions[$definitionKey])) { |
|
|
|
|
375
|
|
|
$definitions[$definitionKey] = []; // Initialize first to prevent infinite loop |
376
|
|
|
$definitions[$definitionKey] = $this->getDefinitionSchema($resourceClass, $resourceMetadata, $definitions, $serializerContext); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
return $definitionKey; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* Gets a definition Schema Object. |
384
|
|
|
* |
385
|
|
|
* @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject |
386
|
|
|
* |
387
|
|
|
* @param string $resourceClass |
388
|
|
|
* @param ResourceMetadata $resourceMetadata |
389
|
|
|
* @param \ArrayObject $definitions |
390
|
|
|
* @param array|null $serializerContext |
391
|
|
|
* |
392
|
|
|
* @return \ArrayObject |
393
|
|
|
*/ |
394
|
|
|
private function getDefinitionSchema(string $resourceClass, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject |
395
|
|
|
{ |
396
|
|
|
$definitionSchema = new \ArrayObject(['type' => 'object']); |
397
|
|
|
|
398
|
|
|
if (null !== $description = $resourceMetadata->getDescription()) { |
399
|
|
|
$definitionSchema['description'] = $description; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
if (null !== $iri = $resourceMetadata->getIri()) { |
403
|
|
|
$definitionSchema['externalDocs'] = ['url' => $iri]; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
$options = isset($serializerContext['groups']) ? ['serializer_groups' => $serializerContext['groups']] : []; |
407
|
|
|
foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) { |
408
|
|
|
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); |
409
|
|
|
$normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName) : $propertyName; |
410
|
|
|
|
411
|
|
|
if ($propertyMetadata->isRequired()) { |
412
|
|
|
$definitionSchema['required'][] = $normalizedPropertyName; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
$definitionSchema['properties'][$normalizedPropertyName] = $this->getPropertySchema($propertyMetadata, $definitions, $serializerContext); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
return $definitionSchema; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
/** |
422
|
|
|
* Gets a property Schema Object. |
423
|
|
|
* |
424
|
|
|
* @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject |
425
|
|
|
* |
426
|
|
|
* @param PropertyMetadata $propertyMetadata |
427
|
|
|
* @param \ArrayObject $definitions |
428
|
|
|
* @param array|null $serializerContext |
429
|
|
|
* |
430
|
|
|
* @return \ArrayObject |
431
|
|
|
*/ |
432
|
|
|
private function getPropertySchema(PropertyMetadata $propertyMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject |
433
|
|
|
{ |
434
|
|
|
$propertySchema = new \ArrayObject(); |
435
|
|
|
|
436
|
|
|
if (false === $propertyMetadata->isWritable()) { |
437
|
|
|
$propertySchema['readOnly'] = true; |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
if (null !== $description = $propertyMetadata->getDescription()) { |
441
|
|
|
$propertySchema['description'] = $description; |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
if (null === $type = $propertyMetadata->getType()) { |
445
|
|
|
return $propertySchema; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
$isCollection = $type->isCollection(); |
449
|
|
|
if (null === $valueType = $isCollection ? $type->getCollectionValueType() : $type) { |
450
|
|
|
$builtinType = 'string'; |
451
|
|
|
$className = null; |
452
|
|
|
} else { |
453
|
|
|
$builtinType = $valueType->getBuiltinType(); |
454
|
|
|
$className = $valueType->getClassName(); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
$valueSchema = $this->getType($builtinType, $isCollection, $className, $propertyMetadata->isReadableLink(), $definitions, $serializerContext); |
458
|
|
|
|
459
|
|
|
return new \ArrayObject((array) $propertySchema + $valueSchema); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
/** |
463
|
|
|
* Gets the Swagger's type corresponding to the given PHP's type. |
464
|
|
|
* |
465
|
|
|
* @param string $type |
466
|
|
|
* @param bool $isCollection |
467
|
|
|
* @param string $className |
468
|
|
|
* @param bool $readableLink |
469
|
|
|
* @param \ArrayObject $definitions |
470
|
|
|
* @param array|null $serializerContext |
471
|
|
|
* |
472
|
|
|
* @return array |
473
|
|
|
*/ |
474
|
|
|
private function getType(string $type, bool $isCollection, string $className = null, bool $readableLink = null, \ArrayObject $definitions, array $serializerContext = null): array |
475
|
|
|
{ |
476
|
|
|
if ($isCollection) { |
477
|
|
|
return ['type' => 'array', 'items' => $this->getType($type, false, $className, $readableLink, $definitions, $serializerContext)]; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
if (Type::BUILTIN_TYPE_STRING === $type) { |
481
|
|
|
return ['type' => 'string']; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
if (Type::BUILTIN_TYPE_INT === $type) { |
485
|
|
|
return ['type' => 'integer']; |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
if (Type::BUILTIN_TYPE_FLOAT === $type) { |
489
|
|
|
return ['type' => 'number']; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
if (Type::BUILTIN_TYPE_BOOL === $type) { |
493
|
|
|
return ['type' => 'boolean']; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
if (Type::BUILTIN_TYPE_OBJECT === $type) { |
497
|
|
|
if (null === $className) { |
498
|
|
|
return ['type' => 'string']; |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
if (is_subclass_of($className, \DateTimeInterface::class)) { |
502
|
|
|
return ['type' => 'string', 'format' => 'date-time']; |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
if (!$this->resourceClassResolver->isResourceClass($className)) { |
506
|
|
|
return ['type' => 'string']; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
if (true === $readableLink) { |
510
|
|
|
return ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, |
511
|
|
|
$this->resourceMetadataFactory->create($className), |
512
|
|
|
$className, $serializerContext) |
513
|
|
|
)]; |
514
|
|
|
} |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
return ['type' => 'string']; |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* Computes the Swagger documentation. |
522
|
|
|
* |
523
|
|
|
* @param Documentation $documentation |
524
|
|
|
* @param \ArrayObject $definitions |
525
|
|
|
* @param \ArrayObject $paths |
526
|
|
|
* |
527
|
|
|
* @return array |
528
|
|
|
*/ |
529
|
|
|
private function computeDoc(Documentation $documentation, \ArrayObject $definitions, \ArrayObject $paths): array |
530
|
|
|
{ |
531
|
|
|
$doc = [ |
532
|
|
|
'swagger' => self::SWAGGER_VERSION, |
533
|
|
|
'basePath' => $this->urlGenerator->generate('api_entrypoint'), |
534
|
|
|
'info' => [ |
535
|
|
|
'title' => $documentation->getTitle(), |
536
|
|
|
'version' => $documentation->getVersion(), |
537
|
|
|
], |
538
|
|
|
'paths' => $paths, |
539
|
|
|
]; |
540
|
|
|
|
541
|
|
|
if ($this->oauthEnabled) { |
542
|
|
|
$doc['securityDefinitions'] = [ |
543
|
|
|
'oauth' => [ |
544
|
|
|
'type' => $this->oauthType, |
545
|
|
|
'description' => 'OAuth client_credentials Grant', |
546
|
|
|
'flow' => $this->oauthFlow, |
547
|
|
|
'tokenUrl' => $this->oauthTokenUrl, |
548
|
|
|
'authorizationUrl' => $this->oauthAuthorizationUrl, |
549
|
|
|
'scopes' => $this->oauthScopes, |
550
|
|
|
], |
551
|
|
|
]; |
552
|
|
|
|
553
|
|
|
$doc['security'] = [['oauth' => []]]; |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
if ('' !== $description = $documentation->getDescription()) { |
557
|
|
|
$doc['info']['description'] = $description; |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
if (count($definitions) > 0) { |
561
|
|
|
$doc['definitions'] = $definitions; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
return $doc; |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
/** |
568
|
|
|
* Gets Swagger parameters corresponding to enabled filters. |
569
|
|
|
* |
570
|
|
|
* @param string $resourceClass |
571
|
|
|
* @param string $operationName |
572
|
|
|
* @param ResourceMetadata $resourceMetadata |
573
|
|
|
* @param \ArrayObject $definitions |
574
|
|
|
* @param array|null $serializerContext |
575
|
|
|
* |
576
|
|
|
* @return array |
577
|
|
|
*/ |
578
|
|
|
private function getFiltersParameters(string $resourceClass, string $operationName, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): array |
579
|
|
|
{ |
580
|
|
|
if (null === $this->filterCollection) { |
581
|
|
|
return []; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
$parameters = []; |
585
|
|
|
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); |
586
|
|
|
foreach ($this->filterCollection as $filterName => $filter) { |
587
|
|
|
if (!in_array($filterName, $resourceFilters, true)) { |
588
|
|
|
continue; |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
foreach ($filter->getDescription($resourceClass) as $name => $data) { |
592
|
|
|
$parameter = [ |
593
|
|
|
'name' => $name, |
594
|
|
|
'in' => 'query', |
595
|
|
|
'required' => $data['required'], |
596
|
|
|
]; |
597
|
|
|
$parameter += $this->getType($data['type'], false, null, null, $definitions, $serializerContext); |
598
|
|
|
|
599
|
|
|
if (isset($data['swagger'])) { |
600
|
|
|
$parameter = $data['swagger'] + $parameter; |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
$parameters[] = $parameter; |
604
|
|
|
} |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
return $parameters; |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
/** |
611
|
|
|
* {@inheritdoc} |
612
|
|
|
*/ |
613
|
|
|
public function supportsNormalization($data, $format = null) |
614
|
|
|
{ |
615
|
|
|
return self::FORMAT === $format && $data instanceof Documentation; |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
/** |
619
|
|
|
* @param bool $collection |
620
|
|
|
* @param bool $denormalization |
621
|
|
|
* @param ResourceMetadata $resourceMetadata |
622
|
|
|
* @param string $operationName |
623
|
|
|
* |
624
|
|
|
* @return array|null |
625
|
|
|
*/ |
626
|
|
|
private function getSerializerContext(bool $collection, bool $denormalization, ResourceMetadata $resourceMetadata, string $operationName) |
627
|
|
|
{ |
628
|
|
|
$contextKey = $denormalization ? 'denormalization_context' : 'normalization_context'; |
629
|
|
|
|
630
|
|
|
if ($collection) { |
631
|
|
|
return $resourceMetadata->getCollectionOperationAttribute($operationName, $contextKey, null, true); |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
return $resourceMetadata->getItemOperationAttribute($operationName, $contextKey, null, true); |
635
|
|
|
} |
636
|
|
|
} |
637
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.