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
|
|
|
namespace ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider; |
13
|
|
|
|
14
|
|
|
use ApiPlatform\Core\Api\FilterCollection; |
15
|
|
|
use ApiPlatform\Core\Api\OperationMethodResolverInterface; |
16
|
|
|
use ApiPlatform\Core\Bridge\NelmioApiDoc\ApiPlatformParser; |
17
|
|
|
use ApiPlatform\Core\Hydra\ApiDocumentationBuilderInterface; |
18
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
19
|
|
|
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; |
20
|
|
|
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; |
21
|
|
|
use Nelmio\ApiDocBundle\Annotation\ApiDoc; |
22
|
|
|
use Nelmio\ApiDocBundle\Extractor\AnnotationsProviderInterface; |
23
|
|
|
use Symfony\Component\HttpFoundation\Request; |
24
|
|
|
use Symfony\Component\Routing\Route; |
25
|
|
|
use Symfony\Component\Routing\RouterInterface; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Creates Nelmio ApiDoc annotations for the api platform. |
29
|
|
|
* |
30
|
|
|
* @author Kévin Dunglas <[email protected]> |
31
|
|
|
*/ |
32
|
|
|
final class ApiPlatformProvider implements AnnotationsProviderInterface |
33
|
|
|
{ |
34
|
|
|
private $resourceNameCollectionFactory; |
35
|
|
|
private $apiDocumentationBuilder; |
36
|
|
|
private $resourceMetadataFactory; |
37
|
|
|
private $filters; |
38
|
|
|
private $operationMethodResolver; |
39
|
|
|
private $router; |
40
|
|
|
|
41
|
|
|
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ApiDocumentationBuilderInterface $apiDocumentationBuilder, ResourceMetadataFactoryInterface $resourceMetadataFactory, FilterCollection $filters, OperationMethodResolverInterface $operationMethodResolver, RouterInterface $router) |
42
|
|
|
{ |
43
|
|
|
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory; |
44
|
|
|
$this->apiDocumentationBuilder = $apiDocumentationBuilder; |
45
|
|
|
$this->resourceMetadataFactory = $resourceMetadataFactory; |
46
|
|
|
$this->filters = $filters; |
47
|
|
|
$this->operationMethodResolver = $operationMethodResolver; |
48
|
|
|
$this->router = $router; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* {@inheritdoc} |
53
|
|
|
*/ |
54
|
|
|
public function getAnnotations() : array |
55
|
|
|
{ |
56
|
|
|
$annotations = []; |
57
|
|
|
$hydraDoc = $this->apiDocumentationBuilder->getApiDocumentation(); |
58
|
|
|
$entrypointHydraDoc = $this->getResourceHydraDoc($hydraDoc, '#Entrypoint'); |
59
|
|
|
|
60
|
|
|
foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { |
61
|
|
|
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
62
|
|
|
|
63
|
|
|
$prefixedShortName = ($iri = $resourceMetadata->getIri()) ? $iri : '#'.$resourceMetadata->getShortName(); |
64
|
|
|
$resourceHydraDoc = $this->getResourceHydraDoc($hydraDoc, $prefixedShortName); |
65
|
|
|
|
66
|
|
|
if ($hydraDoc) { |
|
|
|
|
67
|
|
View Code Duplication |
foreach ($resourceMetadata->getCollectionOperations() as $operationName => $operation) { |
|
|
|
|
68
|
|
|
$annotations[] = $this->getApiDoc(true, $resourceClass, $resourceMetadata, $operationName, $operation, $resourceHydraDoc, $entrypointHydraDoc); |
|
|
|
|
69
|
|
|
} |
70
|
|
|
|
71
|
|
View Code Duplication |
foreach ($resourceMetadata->getItemOperations() as $operationName => $operation) { |
|
|
|
|
72
|
|
|
$annotations[] = $this->getApiDoc(false, $resourceClass, $resourceMetadata, $operationName, $operation, $resourceHydraDoc); |
|
|
|
|
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
return $annotations; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Builds ApiDoc annotation from ApiPlatform data. |
82
|
|
|
* |
83
|
|
|
* @param bool $collection |
84
|
|
|
* @param string $resourceClass |
85
|
|
|
* @param ResourceMetadata $resourceMetadata |
86
|
|
|
* @param string $operationName |
87
|
|
|
* @param array $operation |
88
|
|
|
* @param array $resourceHydraDoc |
89
|
|
|
* @param array $entrypointHydraDoc |
90
|
|
|
* |
91
|
|
|
* @return ApiDoc |
92
|
|
|
*/ |
93
|
|
|
private function getApiDoc(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, array $resourceHydraDoc, array $entrypointHydraDoc = []) : ApiDoc |
|
|
|
|
94
|
|
|
{ |
95
|
|
|
if ($collection) { |
96
|
|
|
$method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); |
97
|
|
|
$operationHydraDoc = $this->getCollectionOperationHydraDoc($resourceMetadata->getShortName(), $method, $entrypointHydraDoc); |
98
|
|
|
} else { |
99
|
|
|
$method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); |
100
|
|
|
$operationHydraDoc = $this->getOperationHydraDoc($method, $resourceHydraDoc); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
$route = $this->getRoute($resourceClass, $collection, $operationName); |
104
|
|
|
|
105
|
|
|
$data = [ |
106
|
|
|
'resource' => $route->getPath(), |
107
|
|
|
'description' => $operationHydraDoc['hydra:title'], |
108
|
|
|
'resourceDescription' => $resourceHydraDoc['hydra:title'], |
109
|
|
|
'section' => $resourceHydraDoc['hydra:title'], |
110
|
|
|
]; |
111
|
|
|
|
112
|
|
View Code Duplication |
if (isset($operationHydraDoc['expects']) && 'owl:Nothing' !== $operationHydraDoc['expects']) { |
|
|
|
|
113
|
|
|
$data['input'] = sprintf('%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
View Code Duplication |
if (isset($operationHydraDoc['returns']) && 'owl:Nothing' !== $operationHydraDoc['returns']) { |
|
|
|
|
117
|
|
|
$data['output'] = sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
if ($collection && Request::METHOD_GET === $method) { |
121
|
|
|
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); |
122
|
|
|
|
123
|
|
|
$data['filters'] = []; |
124
|
|
|
foreach ($this->filters as $filterName => $filter) { |
125
|
|
|
if (in_array($filterName, $resourceFilters)) { |
126
|
|
|
foreach ($filter->getDescription($resource) as $name => $definition) { |
|
|
|
|
127
|
|
|
$data['filters'][] = ['name' => $name] + $definition; |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
$apiDoc = new ApiDoc($data); |
134
|
|
|
$apiDoc->setRoute($route); |
135
|
|
|
|
136
|
|
|
return $apiDoc; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Gets Hydra documentation for the given resource. |
141
|
|
|
* |
142
|
|
|
* @param array $hydraApiDoc |
143
|
|
|
* @param string $prefixedShortName |
144
|
|
|
* |
145
|
|
|
* @return array|null |
146
|
|
|
*/ |
147
|
|
|
private function getResourceHydraDoc(array $hydraApiDoc, string $prefixedShortName) |
148
|
|
|
{ |
149
|
|
|
foreach ($hydraApiDoc['hydra:supportedClass'] as $supportedClass) { |
150
|
|
|
if ($supportedClass['@id'] === $prefixedShortName) { |
151
|
|
|
return $supportedClass; |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Gets the Hydra documentation of a given operation. |
158
|
|
|
* |
159
|
|
|
* @param string $method |
160
|
|
|
* @param array $hydraDoc |
161
|
|
|
* |
162
|
|
|
* @return array|null |
163
|
|
|
*/ |
164
|
|
|
private function getOperationHydraDoc(string $method, array $hydraDoc) |
165
|
|
|
{ |
166
|
|
|
foreach ($hydraDoc['hydra:supportedOperation'] as $supportedOperation) { |
167
|
|
|
if ($supportedOperation['hydra:method'] === $method) { |
168
|
|
|
return $supportedOperation; |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Gets the Hydra documentation for the collection operation. |
175
|
|
|
* |
176
|
|
|
* @param string $shortName |
177
|
|
|
* @param string $method |
178
|
|
|
* @param array $hydraEntrypointDoc |
179
|
|
|
* |
180
|
|
|
* @return array|null |
181
|
|
|
*/ |
182
|
|
|
private function getCollectionOperationHydraDoc(string $shortName, string $method, array $hydraEntrypointDoc) |
183
|
|
|
{ |
184
|
|
|
$propertyName = '#Entrypoint/'.lcfirst($shortName); |
185
|
|
|
|
186
|
|
|
foreach ($hydraEntrypointDoc['hydra:supportedProperty'] as $supportedProperty) { |
187
|
|
|
$hydraProperty = $supportedProperty['hydra:property']; |
188
|
|
|
if ($hydraProperty['@id'] === $propertyName) { |
189
|
|
|
return $this->getOperationHydraDoc($method, $hydraProperty); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Finds the route for an operation on a resource. |
196
|
|
|
* |
197
|
|
|
* @param string $resourceClass |
198
|
|
|
* @param bool $collection |
199
|
|
|
* @param string $operationName |
200
|
|
|
* |
201
|
|
|
* @return Route |
202
|
|
|
* |
203
|
|
|
* @throws \InvalidArgumentException |
204
|
|
|
*/ |
205
|
|
|
private function getRoute(string $resourceClass, bool $collection, string $operationName) : Route |
206
|
|
|
{ |
207
|
|
|
$operationNameKey = sprintf('_%s_operation_name', $collection ? 'collection' : 'item'); |
208
|
|
|
$found = false; |
209
|
|
|
|
210
|
|
|
foreach ($this->router->getRouteCollection()->all() as $routeName => $route) { |
211
|
|
|
$currentResourceClass = $route->getDefault('_resource_class'); |
212
|
|
|
$currentOperationName = $route->getDefault($operationNameKey); |
213
|
|
|
|
214
|
|
|
if ($resourceClass === $currentResourceClass && $operationName === $currentOperationName) { |
215
|
|
|
$found = true; |
216
|
|
|
break; |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
if (!$found) { |
221
|
|
|
throw new \InvalidArgumentException(sprintf('No route found for operation "%s" for type "%s".', $operationName, $resourceClass)); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
return $route; |
|
|
|
|
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
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.