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\Symfony\Bundle\Action; |
15
|
|
|
|
16
|
|
|
use ApiPlatform\Core\Api\FormatsProviderInterface; |
17
|
|
|
use ApiPlatform\Core\Documentation\Documentation; |
18
|
|
|
use ApiPlatform\Core\Exception\InvalidArgumentException; |
19
|
|
|
use ApiPlatform\Core\Exception\RuntimeException; |
20
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
21
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; |
22
|
|
|
use ApiPlatform\Core\Util\RequestAttributesExtractor; |
23
|
|
|
use Symfony\Component\HttpFoundation\Request; |
24
|
|
|
use Symfony\Component\HttpFoundation\Response; |
25
|
|
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
26
|
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Displays the documentation in Swagger UI. |
30
|
|
|
* |
31
|
|
|
* @author Kévin Dunglas <[email protected]> |
32
|
|
|
*/ |
33
|
|
|
final class SwaggerUiAction |
34
|
|
|
{ |
35
|
|
|
private $resourceNameCollectionFactory; |
36
|
|
|
private $resourceMetadataFactory; |
37
|
|
|
private $normalizer; |
38
|
|
|
private $twig; |
39
|
|
|
private $urlGenerator; |
40
|
|
|
private $title; |
41
|
|
|
private $description; |
42
|
|
|
private $version; |
43
|
|
|
private $formats = []; |
44
|
|
|
private $oauthEnabled; |
45
|
|
|
private $oauthClientId; |
46
|
|
|
private $oauthClientSecret; |
47
|
|
|
private $oauthType; |
48
|
|
|
private $oauthFlow; |
49
|
|
|
private $oauthTokenUrl; |
50
|
|
|
private $oauthAuthorizationUrl; |
51
|
|
|
private $oauthScopes; |
52
|
|
|
private $formatsProvider; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @throws InvalidArgumentException |
56
|
|
|
*/ |
57
|
|
|
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, \Twig_Environment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', /* FormatsProviderInterface */ $formatsProvider = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = []) |
58
|
|
|
{ |
59
|
|
|
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory; |
60
|
|
|
$this->resourceMetadataFactory = $resourceMetadataFactory; |
61
|
|
|
$this->normalizer = $normalizer; |
62
|
|
|
$this->twig = $twig; |
63
|
|
|
$this->urlGenerator = $urlGenerator; |
64
|
|
|
$this->title = $title; |
65
|
|
|
$this->description = $description; |
66
|
|
|
$this->version = $version; |
67
|
|
|
$this->oauthEnabled = $oauthEnabled; |
68
|
|
|
$this->oauthClientId = $oauthClientId; |
69
|
|
|
$this->oauthClientSecret = $oauthClientSecret; |
70
|
|
|
$this->oauthType = $oauthType; |
71
|
|
|
$this->oauthFlow = $oauthFlow; |
72
|
|
|
$this->oauthTokenUrl = $oauthTokenUrl; |
73
|
|
|
$this->oauthAuthorizationUrl = $oauthAuthorizationUrl; |
74
|
|
|
$this->oauthScopes = $oauthScopes; |
75
|
|
|
|
76
|
|
|
if (\is_array($formatsProvider)) { |
77
|
|
|
if ($formatsProvider) { |
|
|
|
|
78
|
|
|
// Only trigger notification for non-default argument |
79
|
|
|
@trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED); |
80
|
|
|
} |
81
|
|
|
$this->formats = $formatsProvider; |
82
|
|
|
|
83
|
|
|
return; |
84
|
|
|
} |
85
|
|
|
if (!$formatsProvider instanceof FormatsProviderInterface) { |
86
|
|
|
throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class)); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
$this->formatsProvider = $formatsProvider; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
public function __invoke(Request $request) |
93
|
|
|
{ |
94
|
|
|
// BC check to be removed in 3.0 |
95
|
|
|
if (null !== $this->formatsProvider) { |
96
|
|
|
$this->formats = $this->formatsProvider->getFormatsFromAttributes(RequestAttributesExtractor::extractAttributes($request)); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
$documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version, $this->formats); |
100
|
|
|
|
101
|
|
|
return new Response($this->twig->render('@ApiPlatform/SwaggerUi/index.html.twig', $this->getContext($request, $documentation))); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Gets the base Twig context. |
106
|
|
|
* |
107
|
|
|
* @param Request $request |
108
|
|
|
* |
109
|
|
|
* @return array |
110
|
|
|
*/ |
111
|
|
|
private function getContext(Request $request, Documentation $documentation): array |
112
|
|
|
{ |
113
|
|
|
$context = [ |
114
|
|
|
'title' => $this->title, |
115
|
|
|
'description' => $this->description, |
116
|
|
|
'formats' => $this->formats, |
117
|
|
|
]; |
118
|
|
|
|
119
|
|
|
$swaggerData = [ |
120
|
|
|
'url' => $this->urlGenerator->generate('api_doc', ['format' => 'json']), |
121
|
|
|
'spec' => $this->normalizer->normalize($documentation, 'json', ['base_url' => $request->getBaseUrl()]), |
122
|
|
|
]; |
123
|
|
|
|
124
|
|
|
$swaggerData['oauth'] = [ |
125
|
|
|
'enabled' => $this->oauthEnabled, |
126
|
|
|
'clientId' => $this->oauthClientId, |
127
|
|
|
'clientSecret' => $this->oauthClientSecret, |
128
|
|
|
'type' => $this->oauthType, |
129
|
|
|
'flow' => $this->oauthFlow, |
130
|
|
|
'tokenUrl' => $this->oauthTokenUrl, |
131
|
|
|
'authorizationUrl' => $this->oauthAuthorizationUrl, |
132
|
|
|
'scopes' => $this->oauthScopes, |
133
|
|
|
]; |
134
|
|
|
|
135
|
|
|
if ($request->isMethodSafe(false) && null !== $resourceClass = $request->attributes->get('_api_resource_class')) { |
136
|
|
|
$swaggerData['id'] = $request->attributes->get('id'); |
137
|
|
|
$swaggerData['queryParameters'] = $request->query->all(); |
138
|
|
|
|
139
|
|
|
$metadata = $this->resourceMetadataFactory->create($resourceClass); |
140
|
|
|
$swaggerData['shortName'] = $metadata->getShortName(); |
141
|
|
|
|
142
|
|
|
if (null !== $collectionOperationName = $request->attributes->get('_api_collection_operation_name')) { |
143
|
|
|
$swaggerData['operationId'] = sprintf('%s%sCollection', $collectionOperationName, $swaggerData['shortName']); |
144
|
|
|
} elseif (null !== $itemOperationName = $request->attributes->get('_api_item_operation_name')) { |
145
|
|
|
$swaggerData['operationId'] = sprintf('%s%sItem', $itemOperationName, $swaggerData['shortName']); |
146
|
|
|
} elseif (null !== $subresourceOperationContext = $request->attributes->get('_api_subresource_context')) { |
147
|
|
|
$swaggerData['operationId'] = $subresourceOperationContext['operationId']; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
list($swaggerData['path'], $swaggerData['method']) = $this->getPathAndMethod($swaggerData); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return $context + ['swagger_data' => $swaggerData]; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
private function getPathAndMethod(array $swaggerData): array |
157
|
|
|
{ |
158
|
|
|
foreach ($swaggerData['spec']['paths'] as $path => $operations) { |
159
|
|
|
foreach ($operations as $method => $operation) { |
160
|
|
|
if ($operation['operationId'] === $swaggerData['operationId']) { |
161
|
|
|
return [$path, $method]; |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
throw new RuntimeException(sprintf('The operation "%s" cannot be found in the Swagger specification.', $swaggerData['operationId'])); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
|
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.