Total Complexity | 93 |
Total Lines | 657 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like DocumentationNormalizer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DocumentationNormalizer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
44 | final class DocumentationNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface |
||
45 | { |
||
46 | use FilterLocatorTrait; |
||
47 | |||
48 | const SWAGGER_VERSION = '2.0'; |
||
49 | const FORMAT = 'json'; |
||
50 | const SWAGGER_DEFINITION_NAME = 'swagger_definition_name'; |
||
51 | |||
52 | private $resourceMetadataFactory; |
||
53 | private $propertyNameCollectionFactory; |
||
54 | private $propertyMetadataFactory; |
||
55 | private $resourceClassResolver; |
||
56 | private $operationMethodResolver; |
||
57 | private $operationPathResolver; |
||
58 | private $nameConverter; |
||
59 | private $oauthEnabled; |
||
60 | private $oauthType; |
||
61 | private $oauthFlow; |
||
62 | private $oauthTokenUrl; |
||
63 | private $oauthAuthorizationUrl; |
||
64 | private $oauthScopes; |
||
65 | private $apiKeys; |
||
66 | private $subresourceOperationFactory; |
||
67 | private $paginationEnabled; |
||
68 | private $paginationPageParameterName; |
||
69 | private $clientItemsPerPage; |
||
70 | private $itemsPerPageParameterName; |
||
71 | private $paginationClientEnabled; |
||
72 | private $paginationClientEnabledParameterName; |
||
73 | |||
74 | /** |
||
75 | * @param ContainerInterface|FilterCollection|null $filterLocator The new filter locator or the deprecated filter collection |
||
76 | */ |
||
77 | public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator = null, $filterLocator = null, NameConverterInterface $nameConverter = null, $oauthEnabled = false, $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', array $oauthScopes = [], array $apiKeys = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, $paginationEnabled = true, $paginationPageParameterName = 'page', $clientItemsPerPage = false, $itemsPerPageParameterName = 'itemsPerPage', $paginationClientEnabled = false, $paginationClientEnabledParameterName = 'pagination') |
||
78 | { |
||
79 | if ($urlGenerator) { |
||
80 | @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.1 and will be removed in 3.0.', UrlGeneratorInterface::class, __METHOD__), E_USER_DEPRECATED); |
||
81 | } |
||
82 | |||
83 | $this->setFilterLocator($filterLocator, true); |
||
84 | |||
85 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||
86 | $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; |
||
87 | $this->propertyMetadataFactory = $propertyMetadataFactory; |
||
88 | $this->resourceClassResolver = $resourceClassResolver; |
||
89 | $this->operationMethodResolver = $operationMethodResolver; |
||
90 | $this->operationPathResolver = $operationPathResolver; |
||
91 | $this->nameConverter = $nameConverter; |
||
92 | $this->oauthEnabled = $oauthEnabled; |
||
93 | $this->oauthType = $oauthType; |
||
94 | $this->oauthFlow = $oauthFlow; |
||
95 | $this->oauthTokenUrl = $oauthTokenUrl; |
||
96 | $this->oauthAuthorizationUrl = $oauthAuthorizationUrl; |
||
97 | $this->oauthScopes = $oauthScopes; |
||
98 | $this->subresourceOperationFactory = $subresourceOperationFactory; |
||
99 | $this->paginationEnabled = $paginationEnabled; |
||
100 | $this->paginationPageParameterName = $paginationPageParameterName; |
||
101 | $this->apiKeys = $apiKeys; |
||
102 | $this->subresourceOperationFactory = $subresourceOperationFactory; |
||
103 | $this->clientItemsPerPage = $clientItemsPerPage; |
||
104 | $this->itemsPerPageParameterName = $itemsPerPageParameterName; |
||
105 | $this->paginationClientEnabled = $paginationClientEnabled; |
||
106 | $this->paginationClientEnabledParameterName = $paginationClientEnabledParameterName; |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * {@inheritdoc} |
||
111 | */ |
||
112 | public function normalize($object, $format = null, array $context = []) |
||
113 | { |
||
114 | $mimeTypes = $object->getMimeTypes(); |
||
115 | $definitions = new \ArrayObject(); |
||
116 | $paths = new \ArrayObject(); |
||
117 | |||
118 | foreach ($object->getResourceNameCollection() as $resourceClass) { |
||
119 | $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
||
120 | $resourceShortName = $resourceMetadata->getShortName(); |
||
121 | |||
122 | $this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, OperationType::COLLECTION); |
||
123 | $this->addPaths($paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, $mimeTypes, OperationType::ITEM); |
||
124 | |||
125 | if (null === $this->subresourceOperationFactory) { |
||
126 | continue; |
||
127 | } |
||
128 | |||
129 | foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $subresourceOperation) { |
||
130 | $operationName = 'get'; |
||
131 | $subResourceMetadata = $this->resourceMetadataFactory->create($subresourceOperation['resource_class']); |
||
132 | $serializerContext = $this->getSerializerContext(OperationType::SUBRESOURCE, false, $subResourceMetadata, $operationName); |
||
133 | $responseDefinitionKey = $this->getDefinition($definitions, $subResourceMetadata, $subresourceOperation['resource_class'], $serializerContext); |
||
134 | |||
135 | $pathOperation = new \ArrayObject([]); |
||
136 | $pathOperation['tags'] = $subresourceOperation['shortNames']; |
||
137 | $pathOperation['operationId'] = $operationId; |
||
138 | $pathOperation['produces'] = $mimeTypes; |
||
139 | $pathOperation['summary'] = sprintf('Retrieves %s%s resource%s.', $subresourceOperation['collection'] ? 'the collection of ' : 'a ', $subresourceOperation['shortNames'][0], $subresourceOperation['collection'] ? 's' : ''); |
||
140 | $pathOperation['responses'] = [ |
||
141 | '200' => $subresourceOperation['collection'] ? [ |
||
142 | 'description' => sprintf('%s collection response', $subresourceOperation['shortNames'][0]), |
||
143 | 'schema' => ['type' => 'array', 'items' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)]], |
||
144 | ] : [ |
||
145 | 'description' => sprintf('%s resource response', $subresourceOperation['shortNames'][0]), |
||
146 | 'schema' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)], |
||
147 | ], |
||
148 | '404' => ['description' => 'Resource not found'], |
||
149 | ]; |
||
150 | |||
151 | // Avoid duplicates parameters when there is a filter on a subresource identifier |
||
152 | $parametersMemory = []; |
||
153 | $pathOperation['parameters'] = []; |
||
154 | |||
155 | foreach ($subresourceOperation['identifiers'] as list($identifier, , $hasIdentifier)) { |
||
156 | if (true === $hasIdentifier) { |
||
157 | $pathOperation['parameters'][] = ['name' => $identifier, 'in' => 'path', 'required' => true, 'type' => 'string']; |
||
158 | $parametersMemory[] = $identifier; |
||
159 | } |
||
160 | } |
||
161 | |||
162 | if ($parameters = $this->getFiltersParameters($subresourceOperation['resource_class'], $operationName, $subResourceMetadata, $definitions, $serializerContext)) { |
||
163 | foreach ($parameters as $parameter) { |
||
164 | if (!\in_array($parameter['name'], $parametersMemory, true)) { |
||
165 | $pathOperation['parameters'][] = $parameter; |
||
166 | } |
||
167 | } |
||
168 | } |
||
169 | |||
170 | $paths[$this->getPath($subresourceOperation['shortNames'][0], $subresourceOperation['route_name'], $subresourceOperation, OperationType::SUBRESOURCE)] = new \ArrayObject(['get' => $pathOperation]); |
||
171 | } |
||
172 | } |
||
173 | |||
174 | $definitions->ksort(); |
||
175 | $paths->ksort(); |
||
176 | |||
177 | return $this->computeDoc($object, $definitions, $paths, $context); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Updates the list of entries in the paths collection. |
||
182 | */ |
||
183 | private function addPaths(\ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, array $mimeTypes, string $operationType) |
||
184 | { |
||
185 | if (null === $operations = OperationType::COLLECTION === $operationType ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) { |
||
186 | return; |
||
187 | } |
||
188 | |||
189 | foreach ($operations as $operationName => $operation) { |
||
190 | $path = $this->getPath($resourceShortName, $operationName, $operation, $operationType); |
||
191 | $method = OperationType::ITEM === $operationType ? $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); |
||
192 | |||
193 | $paths[$path][strtolower($method)] = $this->getPathOperation($operationName, $operation, $method, $operationType, $resourceClass, $resourceMetadata, $mimeTypes, $definitions); |
||
194 | } |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Gets the path for an operation. |
||
199 | * |
||
200 | * If the path ends with the optional _format parameter, it is removed |
||
201 | * as optional path parameters are not yet supported. |
||
202 | * |
||
203 | * @see https://github.com/OAI/OpenAPI-Specification/issues/93 |
||
204 | */ |
||
205 | private function getPath(string $resourceShortName, string $operationName, array $operation, string $operationType): string |
||
206 | { |
||
207 | $path = $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName); |
||
208 | if ('.{_format}' === substr($path, -10)) { |
||
209 | $path = substr($path, 0, -10); |
||
210 | } |
||
211 | |||
212 | return $path; |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * Gets a path Operation Object. |
||
217 | * |
||
218 | * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object |
||
219 | * |
||
220 | * @param string[] $mimeTypes |
||
221 | */ |
||
222 | private function getPathOperation(string $operationName, array $operation, string $method, string $operationType, string $resourceClass, ResourceMetadata $resourceMetadata, array $mimeTypes, \ArrayObject $definitions): \ArrayObject |
||
223 | { |
||
224 | $pathOperation = new \ArrayObject($operation['swagger_context'] ?? []); |
||
225 | $resourceShortName = $resourceMetadata->getShortName(); |
||
226 | $pathOperation['tags'] ?? $pathOperation['tags'] = [$resourceShortName]; |
||
227 | $pathOperation['operationId'] ?? $pathOperation['operationId'] = lcfirst($operationName).ucfirst($resourceShortName).ucfirst($operationType); |
||
228 | if ($resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) { |
||
229 | $pathOperation['deprecated'] = true; |
||
230 | } |
||
231 | |||
232 | switch ($method) { |
||
233 | case 'GET': |
||
234 | return $this->updateGetOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); |
||
235 | case 'POST': |
||
236 | return $this->updatePostOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); |
||
237 | case 'PATCH': |
||
238 | $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Updates the %s resource.', $resourceShortName); |
||
239 | // no break |
||
240 | case 'PUT': |
||
241 | return $this->updatePutOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); |
||
242 | case 'DELETE': |
||
243 | return $this->updateDeleteOperation($pathOperation, $resourceShortName); |
||
244 | } |
||
245 | |||
246 | return $pathOperation; |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * @return \ArrayObject |
||
251 | */ |
||
252 | private function updateGetOperation(\ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions) |
||
253 | { |
||
254 | $serializerContext = $this->getSerializerContext($operationType, false, $resourceMetadata, $operationName); |
||
255 | $responseDefinitionKey = $this->getDefinition($definitions, $resourceMetadata, $resourceClass, $serializerContext); |
||
256 | |||
257 | $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; |
||
258 | |||
259 | if (OperationType::COLLECTION === $operationType) { |
||
260 | $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves the collection of %s resources.', $resourceShortName); |
||
261 | $pathOperation['responses'] ?? $pathOperation['responses'] = [ |
||
262 | '200' => [ |
||
263 | 'description' => sprintf('%s collection response', $resourceShortName), |
||
264 | 'schema' => [ |
||
265 | 'type' => 'array', |
||
266 | 'items' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)], |
||
267 | ], |
||
268 | ], |
||
269 | ]; |
||
270 | |||
271 | if (!isset($pathOperation['parameters']) && $parameters = $this->getFiltersParameters($resourceClass, $operationName, $resourceMetadata, $definitions, $serializerContext)) { |
||
272 | $pathOperation['parameters'] = $parameters; |
||
273 | } |
||
274 | |||
275 | if ($this->paginationEnabled && $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_enabled', true, true)) { |
||
276 | $pathOperation['parameters'][] = $this->getPaginationParameters(); |
||
277 | |||
278 | if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $this->clientItemsPerPage, true)) { |
||
279 | $pathOperation['parameters'][] = $this->getItemsPerPageParameters(); |
||
280 | } |
||
281 | } |
||
282 | if ($this->paginationEnabled && $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_enabled', $this->paginationClientEnabled, true)) { |
||
283 | $pathOperation['parameters'][] = $this->getPaginationClientEnabledParameters(); |
||
284 | } |
||
285 | |||
286 | return $pathOperation; |
||
287 | } |
||
288 | |||
289 | $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves a %s resource.', $resourceShortName); |
||
290 | $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[ |
||
291 | 'name' => 'id', |
||
292 | 'in' => 'path', |
||
293 | 'required' => true, |
||
294 | 'type' => 'string', |
||
295 | ]]; |
||
296 | $pathOperation['responses'] ?? $pathOperation['responses'] = [ |
||
297 | '200' => [ |
||
298 | 'description' => sprintf('%s resource response', $resourceShortName), |
||
299 | 'schema' => ['$ref' => sprintf('#/definitions/%s', $responseDefinitionKey)], |
||
300 | ], |
||
301 | '404' => ['description' => 'Resource not found'], |
||
302 | ]; |
||
303 | |||
304 | return $pathOperation; |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * @return \ArrayObject |
||
309 | */ |
||
310 | private function updatePostOperation(\ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions) |
||
311 | { |
||
312 | $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes; |
||
313 | $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; |
||
314 | $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Creates a %s resource.', $resourceShortName); |
||
315 | $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[ |
||
316 | 'name' => lcfirst($resourceShortName), |
||
317 | 'in' => 'body', |
||
318 | 'description' => sprintf('The new %s resource', $resourceShortName), |
||
319 | 'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
||
320 | $this->getSerializerContext($operationType, true, $resourceMetadata, $operationName) |
||
321 | ))], |
||
322 | ]]; |
||
323 | $pathOperation['responses'] ?? $pathOperation['responses'] = [ |
||
324 | '201' => [ |
||
325 | 'description' => sprintf('%s resource created', $resourceShortName), |
||
326 | 'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
||
327 | $this->getSerializerContext($operationType, false, $resourceMetadata, $operationName) |
||
328 | ))], |
||
329 | ], |
||
330 | '400' => ['description' => 'Invalid input'], |
||
331 | '404' => ['description' => 'Resource not found'], |
||
332 | ]; |
||
333 | |||
334 | return $pathOperation; |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * @return \ArrayObject |
||
339 | */ |
||
340 | private function updatePutOperation(\ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions) |
||
341 | { |
||
342 | $pathOperation['consumes'] ?? $pathOperation['consumes'] = $mimeTypes; |
||
343 | $pathOperation['produces'] ?? $pathOperation['produces'] = $mimeTypes; |
||
344 | $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Replaces the %s resource.', $resourceShortName); |
||
345 | $pathOperation['parameters'] ?? $pathOperation['parameters'] = [ |
||
346 | [ |
||
347 | 'name' => 'id', |
||
348 | 'in' => 'path', |
||
349 | 'type' => 'string', |
||
350 | 'required' => true, |
||
351 | ], |
||
352 | [ |
||
353 | 'name' => lcfirst($resourceShortName), |
||
354 | 'in' => 'body', |
||
355 | 'description' => sprintf('The updated %s resource', $resourceShortName), |
||
356 | 'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
||
357 | $this->getSerializerContext($operationType, true, $resourceMetadata, $operationName) |
||
358 | ))], |
||
359 | ], |
||
360 | ]; |
||
361 | $pathOperation['responses'] ?? $pathOperation['responses'] = [ |
||
362 | '200' => [ |
||
363 | 'description' => sprintf('%s resource updated', $resourceShortName), |
||
364 | 'schema' => ['$ref' => sprintf('#/definitions/%s', $this->getDefinition($definitions, $resourceMetadata, $resourceClass, |
||
365 | $this->getSerializerContext($operationType, false, $resourceMetadata, $operationName) |
||
366 | ))], |
||
367 | ], |
||
368 | '400' => ['description' => 'Invalid input'], |
||
369 | '404' => ['description' => 'Resource not found'], |
||
370 | ]; |
||
371 | |||
372 | return $pathOperation; |
||
373 | } |
||
374 | |||
375 | private function updateDeleteOperation(\ArrayObject $pathOperation, string $resourceShortName): \ArrayObject |
||
376 | { |
||
377 | $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Removes the %s resource.', $resourceShortName); |
||
378 | $pathOperation['responses'] ?? $pathOperation['responses'] = [ |
||
379 | '204' => ['description' => sprintf('%s resource deleted', $resourceShortName)], |
||
380 | '404' => ['description' => 'Resource not found'], |
||
381 | ]; |
||
382 | |||
383 | $pathOperation['parameters'] ?? $pathOperation['parameters'] = [[ |
||
384 | 'name' => 'id', |
||
385 | 'in' => 'path', |
||
386 | 'type' => 'string', |
||
387 | 'required' => true, |
||
388 | ]]; |
||
389 | |||
390 | return $pathOperation; |
||
391 | } |
||
392 | |||
393 | private function getDefinition(\ArrayObject $definitions, ResourceMetadata $resourceMetadata, string $resourceClass, array $serializerContext = null): string |
||
394 | { |
||
395 | if (isset($serializerContext[self::SWAGGER_DEFINITION_NAME])) { |
||
396 | $definitionKey = sprintf('%s-%s', $resourceMetadata->getShortName(), $serializerContext[self::SWAGGER_DEFINITION_NAME]); |
||
397 | } else { |
||
398 | $definitionKey = $this->getDefinitionKey($resourceMetadata->getShortName(), (array) ($serializerContext[AbstractNormalizer::GROUPS] ?? [])); |
||
399 | } |
||
400 | |||
401 | if (!isset($definitions[$definitionKey])) { |
||
402 | $definitions[$definitionKey] = []; // Initialize first to prevent infinite loop |
||
403 | $definitions[$definitionKey] = $this->getDefinitionSchema($resourceClass, $resourceMetadata, $definitions, $serializerContext); |
||
404 | } |
||
405 | |||
406 | return $definitionKey; |
||
407 | } |
||
408 | |||
409 | private function getDefinitionKey(string $resourceShortName, array $groups): string |
||
410 | { |
||
411 | return $groups ? sprintf('%s-%s', $resourceShortName, implode('_', $groups)) : $resourceShortName; |
||
412 | } |
||
413 | |||
414 | /** |
||
415 | * Gets a definition Schema Object. |
||
416 | * |
||
417 | * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject |
||
418 | */ |
||
419 | private function getDefinitionSchema(string $resourceClass, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject |
||
420 | { |
||
421 | $definitionSchema = new \ArrayObject(['type' => 'object']); |
||
422 | |||
423 | if (null !== $description = $resourceMetadata->getDescription()) { |
||
424 | $definitionSchema['description'] = $description; |
||
425 | } |
||
426 | |||
427 | if (null !== $iri = $resourceMetadata->getIri()) { |
||
428 | $definitionSchema['externalDocs'] = ['url' => $iri]; |
||
429 | } |
||
430 | |||
431 | $options = isset($serializerContext[AbstractNormalizer::GROUPS]) ? ['serializer_groups' => $serializerContext[AbstractNormalizer::GROUPS]] : []; |
||
432 | foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) { |
||
433 | $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); |
||
434 | $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName) : $propertyName; |
||
435 | |||
436 | if ($propertyMetadata->isRequired()) { |
||
437 | $definitionSchema['required'][] = $normalizedPropertyName; |
||
438 | } |
||
439 | |||
440 | $definitionSchema['properties'][$normalizedPropertyName] = $this->getPropertySchema($propertyMetadata, $definitions, $serializerContext); |
||
441 | } |
||
442 | |||
443 | return $definitionSchema; |
||
444 | } |
||
445 | |||
446 | /** |
||
447 | * Gets a property Schema Object. |
||
448 | * |
||
449 | * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject |
||
450 | */ |
||
451 | private function getPropertySchema(PropertyMetadata $propertyMetadata, \ArrayObject $definitions, array $serializerContext = null): \ArrayObject |
||
452 | { |
||
453 | $propertySchema = new \ArrayObject($propertyMetadata->getAttributes()['swagger_context'] ?? []); |
||
454 | |||
455 | if (false === $propertyMetadata->isWritable()) { |
||
456 | $propertySchema['readOnly'] = true; |
||
457 | } |
||
458 | |||
459 | if (null !== $description = $propertyMetadata->getDescription()) { |
||
460 | $propertySchema['description'] = $description; |
||
461 | } |
||
462 | |||
463 | if (null === $type = $propertyMetadata->getType()) { |
||
464 | return $propertySchema; |
||
465 | } |
||
466 | |||
467 | $isCollection = $type->isCollection(); |
||
468 | if (null === $valueType = $isCollection ? $type->getCollectionValueType() : $type) { |
||
469 | $builtinType = 'string'; |
||
470 | $className = null; |
||
471 | } else { |
||
472 | $builtinType = $valueType->getBuiltinType(); |
||
473 | $className = $valueType->getClassName(); |
||
474 | } |
||
475 | |||
476 | $valueSchema = $this->getType($builtinType, $isCollection, $className, $propertyMetadata->isReadableLink(), $definitions, $serializerContext); |
||
477 | |||
478 | return new \ArrayObject((array) $propertySchema + $valueSchema); |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * Gets the Swagger's type corresponding to the given PHP's type. |
||
483 | * |
||
484 | * @param string $className |
||
485 | * @param bool $readableLink |
||
486 | */ |
||
487 | private function getType(string $type, bool $isCollection, string $className = null, bool $readableLink = null, \ArrayObject $definitions, array $serializerContext = null): array |
||
531 | } |
||
532 | |||
533 | /** |
||
534 | * Computes the Swagger documentation. |
||
535 | */ |
||
536 | private function computeDoc(Documentation $documentation, \ArrayObject $definitions, \ArrayObject $paths, array $context): array |
||
537 | { |
||
538 | $doc = [ |
||
539 | 'swagger' => self::SWAGGER_VERSION, |
||
540 | 'basePath' => $context['base_url'] ?? '/', |
||
541 | 'info' => [ |
||
542 | 'title' => $documentation->getTitle(), |
||
543 | 'version' => $documentation->getVersion(), |
||
544 | ], |
||
545 | 'paths' => $paths, |
||
546 | ]; |
||
547 | |||
548 | $securityDefinitions = []; |
||
549 | $security = []; |
||
550 | |||
551 | if ($this->oauthEnabled) { |
||
552 | $securityDefinitions['oauth'] = [ |
||
553 | 'type' => $this->oauthType, |
||
554 | 'description' => 'OAuth client_credentials Grant', |
||
555 | 'flow' => $this->oauthFlow, |
||
556 | 'tokenUrl' => $this->oauthTokenUrl, |
||
557 | 'authorizationUrl' => $this->oauthAuthorizationUrl, |
||
558 | 'scopes' => $this->oauthScopes, |
||
559 | ]; |
||
560 | |||
561 | $security[] = ['oauth' => []]; |
||
562 | } |
||
563 | |||
564 | if ($this->apiKeys) { |
||
|
|||
565 | foreach ($this->apiKeys as $key => $apiKey) { |
||
566 | $name = $apiKey['name']; |
||
567 | $type = $apiKey['type']; |
||
568 | |||
569 | $securityDefinitions[$key] = [ |
||
570 | 'type' => 'apiKey', |
||
571 | 'in' => $type, |
||
572 | 'description' => sprintf('Value for the %s %s', $name, 'query' === $type ? sprintf('%s parameter', $type) : $type), |
||
573 | 'name' => $name, |
||
574 | ]; |
||
575 | |||
576 | $security[] = [$key => []]; |
||
577 | } |
||
578 | } |
||
579 | |||
580 | if ($securityDefinitions && $security) { |
||
581 | $doc['securityDefinitions'] = $securityDefinitions; |
||
582 | $doc['security'] = $security; |
||
583 | } |
||
584 | |||
585 | if ('' !== $description = $documentation->getDescription()) { |
||
586 | $doc['info']['description'] = $description; |
||
587 | } |
||
588 | |||
589 | if (\count($definitions) > 0) { |
||
590 | $doc['definitions'] = $definitions; |
||
591 | } |
||
592 | |||
593 | return $doc; |
||
594 | } |
||
595 | |||
596 | /** |
||
597 | * Gets Swagger parameters corresponding to enabled filters. |
||
598 | */ |
||
599 | private function getFiltersParameters(string $resourceClass, string $operationName, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, array $serializerContext = null): array |
||
600 | { |
||
601 | if (null === $this->filterLocator) { |
||
602 | return []; |
||
603 | } |
||
604 | |||
605 | $parameters = []; |
||
606 | $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); |
||
607 | foreach ($resourceFilters as $filterId) { |
||
608 | if (!$filter = $this->getFilter($filterId)) { |
||
609 | continue; |
||
610 | } |
||
611 | |||
612 | foreach ($filter->getDescription($resourceClass) as $name => $data) { |
||
613 | $parameter = [ |
||
614 | 'name' => $name, |
||
615 | 'in' => 'query', |
||
616 | 'required' => $data['required'], |
||
617 | ]; |
||
618 | $parameter += $this->getType($data['type'], false, null, null, $definitions, $serializerContext); |
||
619 | |||
620 | if (isset($data['swagger'])) { |
||
621 | $parameter = $data['swagger'] + $parameter; |
||
622 | } |
||
623 | |||
624 | $parameters[] = $parameter; |
||
625 | } |
||
626 | } |
||
627 | |||
628 | return $parameters; |
||
629 | } |
||
630 | |||
631 | /** |
||
632 | * Returns pagination parameters for the "get" collection operation. |
||
633 | */ |
||
634 | private function getPaginationParameters(): array |
||
635 | { |
||
636 | return [ |
||
637 | 'name' => $this->paginationPageParameterName, |
||
638 | 'in' => 'query', |
||
639 | 'required' => false, |
||
640 | 'type' => 'integer', |
||
641 | 'description' => 'The collection page number', |
||
642 | ]; |
||
643 | } |
||
644 | |||
645 | /** |
||
646 | * Returns enable pagination parameter for the "get" collection operation |
||
647 | */ |
||
648 | private function getPaginationClientEnabledParameters(): array |
||
649 | { |
||
650 | return [ |
||
651 | 'name' => $this->paginationClientEnabledParameterName, |
||
652 | 'in' => 'query', |
||
653 | 'required' => false, |
||
654 | 'type' => 'boolean', |
||
655 | 'description' => 'Enable or disable pagination', |
||
656 | ]; |
||
657 | } |
||
658 | |||
659 | /** |
||
660 | * Returns items per page parameters for the "get" collection operation. |
||
661 | */ |
||
662 | private function getItemsPerPageParameters(): array |
||
670 | ]; |
||
671 | } |
||
672 | |||
673 | /** |
||
674 | * {@inheritdoc} |
||
675 | */ |
||
676 | public function supportsNormalization($data, $format = null) |
||
677 | { |
||
678 | return self::FORMAT === $format && $data instanceof Documentation; |
||
679 | } |
||
680 | |||
681 | /** |
||
682 | * {@inheritdoc} |
||
683 | */ |
||
684 | public function hasCacheableSupportsMethod(): bool |
||
685 | { |
||
686 | return true; |
||
687 | } |
||
688 | |||
689 | /** |
||
690 | * @return array|null |
||
691 | */ |
||
692 | private function getSerializerContext(string $operationType, bool $denormalization, ResourceMetadata $resourceMetadata, string $operationName) |
||
701 | } |
||
702 | } |
||
703 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.