Passed
Pull Request — master (#34)
by Anatoly
02:27
created

OpenApi::setVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Fenric <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Fenric
8
 * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-router
10
 */
11
12
namespace Sunrise\Http\Router\OpenApi;
13
14
/**
15
 * Import classes
16
 */
17
use Doctrine\Common\Annotations\SimpleAnnotationReader;
18
use Sunrise\Http\Router\Annotation\OpenApi\Operation;
19
use Sunrise\Http\Router\Annotation\OpenApi\Parameter;
20
use Sunrise\Http\Router\Annotation\OpenApi\Schema;
21
use Sunrise\Http\Router\RouteInterface;
22
use ReflectionClass;
23
24
/**
25
 * Import functions
26
 */
27
use function Sunrise\Http\Router\path_parse;
0 ignored issues
show
introduced by
The function Sunrise\Http\Router\path_parse was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
28
use function array_walk_recursive;
29
use function class_exists;
30
use function str_replace;
31
use function strtolower;
32
33
/**
34
 * OpenApi
35
 */
36
class OpenApi
37
{
38
39
    /**
40
     * Version of OpenAPI specification
41
     *
42
     * @var string
43
     */
44
    public const VERSION = '3.0.2';
45
46
    /**
47
     * @var SimpleAnnotationReader
48
     */
49
    private $annotationReader;
50
51
    /**
52
     * @var RouteInterface[]
53
     */
54
    private $routes = [];
55
56
    /**
57
     * @var string
58
     */
59
    private $title = 'REST API';
60
61
    /**
62
     * @var string
63
     */
64
    private $version = '0.0.1';
65
66
    /**
67
     * @var array
68
     */
69
    private $description = [];
70
71
    /**
72
     * @var array
73
     */
74
    private $schemasMap = [];
75
76
    /**
77
     * Constructor of the class
78
     */
79
    public function __construct()
80
    {
81
        $this->annotationReader = new SimpleAnnotationReader();
82
        $this->annotationReader->addNamespace('Sunrise\Http\Router\Annotation');
83
    }
84
85
    /**
86
     * @param RouteInterface ...$routes
87
     *
88
     * @return void
89
     */
90
    public function addRoute(RouteInterface ...$routes) : void
91
    {
92
        foreach ($routes as $route) {
93
            $this->routes[] = $route;
94
        }
95
    }
96
97
    /**
98
     * @param string $title
99
     *
100
     * @return void
101
     */
102
    public function setTitle(string $title) : void
103
    {
104
        $this->title = $title;
105
    }
106
107
    /**
108
     * @param string $version
109
     *
110
     * @return void
111
     */
112
    public function setVersion(string $version) : void
113
    {
114
        $this->version = $version;
115
    }
116
117
    /**
118
     * @return void
119
     */
120
    public function describe() : void
121
    {
122
        $this->description['openapi'] = self::VERSION;
123
124
        $this->description['info']['title'] = $this->title;
125
        $this->description['info']['version'] = $this->version;
126
127
        foreach ($this->routes as $route) {
128
            $path = $this->createPatternedPathFromRoute($route);
129
            $operation = $this->createOperationAnnotationFromRoute($route);
130
131
            foreach ($route->getMethods() as $method) {
132
                $method = strtolower($method);
133
134
                $this->description['paths'][$path][$method]['operationId'] = $route->getName();
135
                $this->description['paths'][$path][$method] += $operation->toArray();
136
            }
137
        }
138
139
        $this->handleComponentSchemas();
140
    }
141
142
    /**
143
     * @return array
144
     */
145
    public function toArray() : array
146
    {
147
        return $this->description;
148
    }
149
150
    /**
151
     * @param RouteInterface $route
152
     *
153
     * @return string
154
     *
155
     * @link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#patterned-fields
156
     */
157
    private function createPatternedPathFromRoute(RouteInterface $route) : string
158
    {
159
        $path = $route->getPath();
160
        $attributes = path_parse($path);
0 ignored issues
show
Bug introduced by
The function path_parse was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

160
        $attributes = /** @scrutinizer ignore-call */ path_parse($path);
Loading history...
161
162
        foreach ($attributes as $attribute) {
163
            $path = str_replace($attribute['raw'], '{' . $attribute['name'] . '}', $path);
164
        }
165
166
        return str_replace(['(', ')'], '', $path);
167
    }
168
169
    /**
170
     * @param RouteInterface $route
171
     *
172
     * @return Operation
173
     */
174
    private function createOperationAnnotationFromRoute(RouteInterface $route) : Operation
175
    {
176
        $source = $route->getRequestHandler();
177
178
        $operation = $this->annotationReader->getClassAnnotation(new ReflectionClass($source), Operation::class);
179
180
        if (!$operation) {
181
            $operation = new Operation();
182
        }
183
184
        $attributes = path_parse($route->getPath());
0 ignored issues
show
Bug introduced by
The function path_parse was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

184
        $attributes = /** @scrutinizer ignore-call */ path_parse($route->getPath());
Loading history...
185
186
        foreach ($attributes as $attribute) {
187
            $parameter = new Parameter();
188
            $parameter->in = 'path';
189
            $parameter->name = $attribute['name'];
190
            $parameter->required = $attribute['isOptional'];
191
192
            $operation->parameters[] = $parameter;
193
        }
194
195
        return $operation;
196
    }
197
198
    /**
199
     * @return void
200
     */
201
    private function handleComponentSchemas() : void
202
    {
203
        array_walk_recursive($this->description, function (&$value, $key) {
204
            if (!('$ref' === $key && null !== $value && class_exists($value))) {
205
                return;
206
            }
207
208
            // the schema already exists...
209
            if (isset($this->description['components']['schemas'][$value])) {
210
                $value = $this->schemasMap[$value];
211
212
                return;
213
            }
214
215
            $schema = $this->annotationReader->getClassAnnotation(new ReflectionClass($value), Schema::class);
216
217
            if (!$schema) {
218
                return;
219
            }
220
221
            $this->description['components']['schemas'][$value]['type'] = 'object';
222
            $this->description['components']['schemas'][$value] += $schema->toArray();
223
224
            $this->schemasMap[$value] = '#/components/schemas/' . $value;
225
            $value = $this->schemasMap[$value];
226
        });
227
    }
228
}
229