Passed
Push — master ( 8bd912...d93388 )
by Alan
06:58 queued 02:20
created

AnnotationsProvider/ApiPlatformProvider.php (1 issue)

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\Serializer\Normalizer\NormalizerInterface;
28
29
/**
30
 * Creates Nelmio ApiDoc annotations for the api platform.
31
 *
32
 * @author Kévin Dunglas <[email protected]>
33
 * @author Teoh Han Hui <[email protected]>
34
 *
35
 * @deprecated since version 2.2, to be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.
36
 */
37
final class ApiPlatformProvider implements AnnotationsProviderInterface
38
{
39
    use FilterLocatorTrait;
40
41
    private $resourceNameCollectionFactory;
42
    private $documentationNormalizer;
43
    private $resourceMetadataFactory;
44
    private $operationMethodResolver;
45
46
    /**
47
     * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection
48
     */
49
    public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, NormalizerInterface $documentationNormalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator, OperationMethodResolverInterface $operationMethodResolver)
50
    {
51
        @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);
52
53
        $this->setFilterLocator($filterLocator);
54
55
        $this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
56
        $this->documentationNormalizer = $documentationNormalizer;
57
        $this->resourceMetadataFactory = $resourceMetadataFactory;
58
        $this->operationMethodResolver = $operationMethodResolver;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function getAnnotations(): array
65
    {
66
        $resourceNameCollection = $this->resourceNameCollectionFactory->create();
67
68
        $hydraDoc = $this->documentationNormalizer->normalize(new Documentation($resourceNameCollection));
69
        if (!\is_array($hydraDoc)) {
70
            throw new \UnexpectedValueException('Expected data to be an array');
71
        }
72
73
        if (empty($hydraDoc)) {
74
            return [];
75
        }
76
77
        $entrypointHydraDoc = $this->getResourceHydraDoc($hydraDoc, '#Entrypoint');
78
        if (null === $entrypointHydraDoc) {
79
            return [];
80
        }
81
82
        $annotations = [];
83
        foreach ($resourceNameCollection as $resourceClass) {
84
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
85
86
            $prefixedShortName = ($iri = $resourceMetadata->getIri()) ? $iri : '#'.$resourceMetadata->getShortName();
87
            $resourceHydraDoc = $this->getResourceHydraDoc($hydraDoc, $prefixedShortName);
88
            if (null === $resourceHydraDoc) {
89
                continue;
90
            }
91
92
            if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) {
93
                foreach ($collectionOperations as $operationName => $operation) {
94
                    $annotations[] = $this->getApiDoc(true, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc, $entrypointHydraDoc);
95
                }
96
            }
97
98
            if (null !== $itemOperations = $resourceMetadata->getItemOperations()) {
99
                foreach ($itemOperations as $operationName => $operation) {
100
                    $annotations[] = $this->getApiDoc(false, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc);
101
                }
102
            }
103
        }
104
105
        return $annotations;
106
    }
107
108
    /**
109
     * Builds ApiDoc annotation from ApiPlatform data.
110
     */
111
    private function getApiDoc(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $resourceHydraDoc, array $entrypointHydraDoc = []): ApiDoc
112
    {
113
        if ($collection) {
114
            $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName);
115
            $route = $this->operationMethodResolver->getCollectionOperationRoute($resourceClass, $operationName);
116
            $operationHydraDoc = $this->getCollectionOperationHydraDoc($resourceMetadata->getShortName(), $method, $entrypointHydraDoc);
0 ignored issues
show
It seems like $resourceMetadata->getShortName() can also be of type null; however, parameter $shortName of ApiPlatform\Core\Bridge\...tionOperationHydraDoc() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

116
            $operationHydraDoc = $this->getCollectionOperationHydraDoc(/** @scrutinizer ignore-type */ $resourceMetadata->getShortName(), $method, $entrypointHydraDoc);
Loading history...
117
        } else {
118
            $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName);
119
            $route = $this->operationMethodResolver->getItemOperationRoute($resourceClass, $operationName);
120
            $operationHydraDoc = $this->getOperationHydraDoc($method, $resourceHydraDoc);
121
        }
122
123
        $data = [
124
            'resource' => $route->getPath(),
125
            'description' => $operationHydraDoc['hydra:title'] ?? '',
126
            'resourceDescription' => $resourceHydraDoc['hydra:title'] ?? '',
127
            'section' => $resourceHydraDoc['hydra:title'] ?? '',
128
        ];
129
130
        if (isset($operationHydraDoc['expects']) && 'owl:Nothing' !== $operationHydraDoc['expects']) {
131
            $data['input'] = sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass, $operationName);
132
        }
133
134
        if (isset($operationHydraDoc['returns']) && 'owl:Nothing' !== $operationHydraDoc['returns']) {
135
            $data['output'] = sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass, $operationName);
136
        }
137
138
        if ($collection && 'GET' === $method) {
139
            $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
140
141
            $data['filters'] = [];
142
            foreach ($resourceFilters as $filterId) {
143
                if ($filter = $this->getFilter($filterId)) {
144
                    foreach ($filter->getDescription($resourceClass) as $name => $definition) {
145
                        $data['filters'][] = ['name' => $name] + $definition;
146
                    }
147
                }
148
            }
149
        }
150
151
        $apiDoc = new ApiDoc($data);
152
        $apiDoc->setRoute($route);
153
154
        return $apiDoc;
155
    }
156
157
    /**
158
     * Gets Hydra documentation for the given resource.
159
     */
160
    private function getResourceHydraDoc(array $hydraApiDoc, string $prefixedShortName): ?array
161
    {
162
        if (!isset($hydraApiDoc['hydra:supportedClass']) || !\is_array($hydraApiDoc['hydra:supportedClass'])) {
163
            return null;
164
        }
165
166
        foreach ($hydraApiDoc['hydra:supportedClass'] as $supportedClass) {
167
            if (isset($supportedClass['@id']) && $supportedClass['@id'] === $prefixedShortName) {
168
                return $supportedClass;
169
            }
170
        }
171
172
        return null;
173
    }
174
175
    /**
176
     * Gets the Hydra documentation of a given operation.
177
     */
178
    private function getOperationHydraDoc(string $method, array $hydraDoc): array
179
    {
180
        if (!isset($hydraDoc['hydra:supportedOperation']) || !\is_array($hydraDoc['hydra:supportedOperation'])) {
181
            return [];
182
        }
183
184
        foreach ($hydraDoc['hydra:supportedOperation'] as $supportedOperation) {
185
            if ($supportedOperation['hydra:method'] === $method) {
186
                return $supportedOperation;
187
            }
188
        }
189
190
        return [];
191
    }
192
193
    /**
194
     * Gets the Hydra documentation for the collection operation.
195
     */
196
    private function getCollectionOperationHydraDoc(string $shortName, string $method, array $hydraEntrypointDoc): array
197
    {
198
        if (!isset($hydraEntrypointDoc['hydra:supportedProperty']) || !\is_array($hydraEntrypointDoc['hydra:supportedProperty'])) {
199
            return [];
200
        }
201
202
        $propertyName = '#Entrypoint/'.lcfirst($shortName);
203
204
        foreach ($hydraEntrypointDoc['hydra:supportedProperty'] as $supportedProperty) {
205
            if (isset($supportedProperty['hydra:property']['@id'])
206
                && $supportedProperty['hydra:property']['@id'] === $propertyName) {
207
                return $this->getOperationHydraDoc($method, $supportedProperty['hydra:property']);
208
            }
209
        }
210
211
        return [];
212
    }
213
}
214