Completed
Push — master ( c762b0...684d44 )
by John
02:56
created

OpenApiBuilder::build()   D

Complexity

Conditions 9
Paths 16

Size

Total Lines 41
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 4.909
c 0
b 0
f 0
cc 9
eloc 23
nc 16
nop 0
1
<?php declare(strict_types = 1);
2
/*
3
 * This file is part of the KleijnWeb\ApiDescriptions package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
namespace KleijnWeb\ApiDescriptions\Description\Builder;
9
10
use KleijnWeb\ApiDescriptions\Description\ComplexType;
11
use KleijnWeb\ApiDescriptions\Description\Description;
12
use KleijnWeb\ApiDescriptions\Description\Operation;
13
use KleijnWeb\ApiDescriptions\Description\Parameter;
14
use KleijnWeb\ApiDescriptions\Description\Path;
15
use KleijnWeb\ApiDescriptions\Description\Response;
16
use KleijnWeb\ApiDescriptions\Description\Schema\ObjectSchema;
17
use KleijnWeb\ApiDescriptions\Description\Schema\Schema;
18
use KleijnWeb\ApiDescriptions\Description\Visitor\ClosureVisitor;
19
use KleijnWeb\ApiDescriptions\Description\Visitor\ClosureVisitorScope;
20
21
/**
22
 * @author John Kleijn <[email protected]>
23
 */
24
class OpenApiBuilder extends Builder implements ClosureVisitorScope
25
{
26
    /**
27
     * @return Description
28
     */
29
    public function build(): Description
30
    {
31
        $host    = isset($this->document->host) ? $this->document->host : null;
32
        $schemes = isset($this->document->schemes) ? $this->document->schemes : [];
33
        $paths   = [];
34
        if (isset($this->document->paths)) {
35
            foreach ($this->document->paths as $path => $pathItem) {
36
                $paths[$path] = $this->createPath($path, $pathItem);
37
            }
38
        }
39
40
        $description = new Description($paths, [], $host, $schemes, $this->document);
0 ignored issues
show
Compatibility introduced by
$this->document of type object<stdClass> is not a sub-type of object<KleijnWeb\ApiDesc...tion\Document\Document>. It seems like you assume a child class of the class stdClass to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
41
42
        /** @var ObjectSchema[] $typeDefinitions */
43
        $typeDefinitions = [];
44
45
        $description->accept(new ClosureVisitor($this, function ($schema) use (&$typeDefinitions) {
46
            if ($schema instanceof ObjectSchema) {
47
                if ($schema->isType(Schema::TYPE_OBJECT) && isset($schema->getDefinition()->{'x-ref-id'})) {
48
                    $typeName = substr(
49
                        $schema->getDefinition()->{'x-ref-id'},
50
                        strrpos($schema->getDefinition()->{'x-ref-id'}, '/') + 1
51
                    );
52
53
                    $typeDefinitions[$typeName] = $schema;
54
                }
55
            }
56
        }));
57
58
        foreach ($typeDefinitions as $name => $schema) {
59
            $type = new ComplexType($name, $schema);
60
            $schema->setComplexType($type);
61
            $complexTypes[] = $type;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$complexTypes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $complexTypes = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
62
        }
63
64
        $description->accept(new ClosureVisitor($description, function () use (&$complexTypes) {
65
            $this->complexTypes = $complexTypes;
0 ignored issues
show
Bug introduced by
The property complexTypes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
66
        }));
67
68
        return $description;
69
    }
70
71
    /**
72
     * @param string    $pathName
73
     * @param \stdClass $definition
74
     *
75
     * @return Path
76
     */
77 View Code Duplication
    protected function createPath(string $pathName, \stdClass $definition)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
78
    {
79
        $pathParameters = $this->extractParameters($definition);
80
81
        /** @var Operation[] $operations */
82
        $operations = [];
83
        foreach (self::$methodNames as $method) {
84
            if (isset($definition->$method)) {
85
                $operations[$method] = $this->createOperation(
86
                    $definition->$method,
87
                    $pathName,
88
                    $method,
89
                    $pathParameters
90
                );
91
            }
92
        }
93
94
        return new Path($pathName, $operations, $pathParameters);
95
    }
96
97
    /**
98
     * @param \stdClass $definition
99
     * @param string    $path
100
     * @param string    $method
101
     * @param array     $pathParameters
102
     *
103
     * @return Operation
104
     */
105
    protected function createOperation(
106
        \stdClass $definition,
107
        string $path,
108
        string $method,
109
        array $pathParameters = []
110
    ): Operation {
111
    
112
113
114
        /** @var Parameter[] $parameters */
115
        $parameters = array_merge($pathParameters, self::extractParameters($definition));
116
        $responses  = [];
117
118
        if (isset($definition->responses)) {
119
            $hasOkResponse = false;
120
            foreach ($definition->responses as $code => $responseDefinition) {
121
                $code = (string)$code;
122
                if ($code === 'default' || substr((string)$code, 1) === '2') {
123
                    $hasOkResponse = true;
124
                }
125
                $code             = (int)$code;
126
                $responses[$code] = $this->createResponse($code, $responseDefinition);
127
            }
128
            if (!$hasOkResponse) {
129
                $responses[200] = $this->createResponse(200, (object)[]);
130
            }
131
        }
132
133
        $schemaDefinition = (object)[];
134
        if (!isset($definition->parameters)) {
135
            $schemaDefinition->type = 'null';
136
            $requestSchema          = $this->schemaFactory->create($schemaDefinition);
137
        } else {
138
            $schemaDefinition->type       = 'object';
139
            $schemaDefinition->required   = [];
140
            $schemaDefinition->properties = (object)[];
141
142 View Code Duplication
            foreach ($parameters as $parameter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
                if ($parameter->isRequired()) {
144
                    $schemaDefinition->required[] = $parameter->getName();
145
                }
146
                $schemaDefinition->properties->{$parameter->getName()} = $parameter->getSchema()->getDefinition();
147
            }
148
149
            $requestSchema = $this->schemaFactory->create($schemaDefinition);
150
        }
151
152
        return new Operation($path, $method, $parameters, $requestSchema, $responses);
153
    }
154
155
    /**
156
     * @param \stdClass $definition
157
     *
158
     * @return array
159
     */
160
    protected function extractParameters(\stdClass $definition)
161
    {
162
        $parameters = [];
163
164
        if (isset($definition->parameters)) {
165
            foreach ($definition->parameters as $parameterDefinition) {
166
                $parameters[] = $this->createParameter($parameterDefinition);
167
            }
168
        }
169
170
        return $parameters;
171
    }
172
173
    /**
174
     * @param \stdClass $definition
175
     *
176
     * @return Parameter
177
     */
178
    protected function createParameter(\stdClass $definition)
179
    {
180
        if ($definition->in === Parameter::IN_BODY) {
181
            $definition->schema       = isset($definition->schema) ? $definition->schema : (object)[];
182
            $definition->schema->type = 'object';
183
        }
184
        if (isset($definition->schema)) {
185
            $schema = $this->schemaFactory->create($definition->schema);
186
        } else {
187
            $schema = $this->createParameterSchema($definition);
188
        }
189
190
        $required         = isset($definition->required) && $definition->required;
191
        $collectionFormat = isset($definition->collectionFormat) ? $definition->collectionFormat : null;
192
193
        return new Parameter($definition->name, $required, $schema, $definition->in, $collectionFormat);
194
    }
195
196
    /**
197
     * @param int       $code
198
     * @param \stdClass $definition
199
     *
200
     * @return Response
201
     */
202 View Code Duplication
    protected function createResponse(int $code, \stdClass $definition)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        return new Response(
205
            $code,
206
            $this->schemaFactory->create(isset($definition->schema) ? $definition->schema : null)
207
        );
208
    }
209
210
    /**
211
     * @param \stdClass $definition
212
     *
213
     * @return Schema
214
     */
215 View Code Duplication
    protected function createParameterSchema(\stdClass $definition): Schema
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
    {
217
        // Remove non-JSON-Schema properties
218
        $schemaDefinition     = clone $definition;
219
        $swaggerPropertyNames = [
220
            'name',
221
            'in',
222
            'description',
223
            'required',
224
            'allowEmptyValue',
225
            'collectionFormat'
226
        ];
227
        foreach ($swaggerPropertyNames as $propertyName) {
228
            if (property_exists($schemaDefinition, $propertyName)) {
229
                unset($schemaDefinition->$propertyName);
230
            }
231
        }
232
233
        return $this->schemaFactory->create($schemaDefinition);
234
    }
235
}
236