Passed
Push — master ( 9b5861...5bdc47 )
by Joao
06:11 queued 16s
created

SwaggerSchema   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 260
Duplicated Lines 8.46 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 8
dl 22
loc 260
rs 6
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A getSpecificationVersion() 0 4 1
A getServerUrl() 0 4 2
A getHttpSchema() 0 4 2
A getHost() 0 4 2
A getBasePath() 0 9 5
B getPathDefinition() 0 50 9
C validateArguments() 10 35 16
B getDefintion() 12 26 8
A getRequestParameters() 0 16 4
A getResponseParameters() 0 10 2
A isAllowNullValues() 0 4 1
A setAllowNullValues() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SwaggerSchema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SwaggerSchema, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ByJG\Swagger;
4
5
use ByJG\Swagger\Exception\DefinitionNotFoundException;
6
use ByJG\Swagger\Exception\HttpMethodNotFoundException;
7
use ByJG\Swagger\Exception\InvalidDefinitionException;
8
use ByJG\Swagger\Exception\NotMatchedException;
9
use ByJG\Swagger\Exception\PathNotFoundException;
10
use ByJG\Util\Uri;
11
12
class SwaggerSchema
13
{
14
    protected $jsonFile;
15
    protected $allowNullValues;
16
    protected $specificationVersion;
17
18
    const SWAGGER_PATHS="paths";
19
    const SWAGGER_PARAMETERS="parameters";
20
    const SWAGGER_COMPONENTS="components";
21
22
    public function __construct($jsonFile, $allowNullValues = false)
23
    {
24
        $this->jsonFile = json_decode($jsonFile, true);
25
        $this->allowNullValues = (bool) $allowNullValues;
26
        $this->specificationVersion = isset($this->jsonFile['swagger']) ? '2' : '3';
27
    }
28
29
    /**
30
     * Returns the major specification version
31
     * @return string
32
     */
33
    public function getSpecificationVersion()
34
    {
35
        return $this->specificationVersion;
36
    }
37
38
    public function getServerUrl()
39
    {
40
        return isset($this->jsonFile['servers']) ? $this->jsonFile['servers'][0]['url'] : '';
41
    }
42
43
    public function getHttpSchema()
44
    {
45
        return isset($this->jsonFile['schemes']) ? $this->jsonFile['schemes'][0] : '';
46
    }
47
48
    public function getHost()
49
    {
50
        return isset($this->jsonFile['host']) ? $this->jsonFile['host'] : '';
51
    }
52
53
    public function getBasePath()
54
    {
55
        if ($this->getSpecificationVersion() === '3') {
56
            $basePath =isset($this->jsonFile['servers']) ? explode('/', $this->jsonFile['servers'][0]['url']) : '';
57
            return is_array($basePath) ? '/' . end($basePath) : $basePath;
58
        }
59
60
        return isset($this->jsonFile['basePath']) ? $this->jsonFile['basePath'] : '';
61
    }
62
63
    /**
64
     * @param $path
65
     * @param $method
66
     * @return mixed
67
     * @throws DefinitionNotFoundException
68
     * @throws HttpMethodNotFoundException
69
     * @throws InvalidDefinitionException
70
     * @throws NotMatchedException
71
     * @throws PathNotFoundException
72
     */
73
    public function getPathDefinition($path, $method)
74
    {
75
        $method = strtolower($method);
76
77
        $path = preg_replace('~^' . $this->getBasePath() . '~', '', $path);
78
79
        $uri = new Uri($path);
80
81
        // Try direct match
82
        if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()])) {
83
            if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method])) {
84
                return $this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method];
85
            }
86
            throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'");
87
        }
88
89
        // Try inline parameter
90
        foreach (array_keys($this->jsonFile[self::SWAGGER_PATHS]) as $pathItem) {
91
            if (strpos($pathItem, '{') === false) {
92
                continue;
93
            }
94
95
            $pathItemPattern = '~^' . preg_replace('~{(.*?)}~', '(?<\1>[^/]+)', $pathItem) . '$~';
96
97
            $matches = [];
98
            if (preg_match($pathItemPattern, $uri->getPath(), $matches)) {
99
                $pathDef = $this->jsonFile[self::SWAGGER_PATHS][$pathItem];
100
                if (!isset($pathDef[$method])) {
101
                    throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'");
102
                }
103
104
                $parametersPathMethod = [];
105
                $parametersPath = [];
106
107
                if (isset($pathDef[$method][self::SWAGGER_PARAMETERS])) {
108
                    $parametersPathMethod = $pathDef[$method][self::SWAGGER_PARAMETERS];
109
                }
110
111
                if (isset($pathDef[self::SWAGGER_PARAMETERS])) {
112
                    $parametersPath = $pathDef[self::SWAGGER_PARAMETERS];
113
                }
114
115
                $this->validateArguments('path', array_merge($parametersPathMethod, $parametersPath), $matches);
116
117
                return $pathDef[$method];
118
            }
119
        }
120
121
        throw new PathNotFoundException('Path "' . $path . '" not found');
122
    }
123
124
    /**
125
     * @param $parameterIn
126
     * @param $parameters
127
     * @param $arguments
128
     * @throws DefinitionNotFoundException
129
     * @throws InvalidDefinitionException
130
     * @throws NotMatchedException
131
     */
132
    private function validateArguments($parameterIn, $parameters, $arguments)
133
    {
134
        if ($this->getSpecificationVersion() === '3') {
135
            foreach ($parameters as $parameter) {
136
                if (isset($parameter['$ref'])) {
137
                    $paramParts = explode("/", $parameter['$ref']);
138
                    if (count($paramParts) != 4 || $paramParts[0] != "#" || $paramParts[1] != self::SWAGGER_COMPONENTS || $paramParts[2] != self::SWAGGER_PARAMETERS) {
139
                        throw new InvalidDefinitionException(
140
                            "Not get the reference in the expected format #/components/parameters/<NAME>"
141
                        );
142
                    }
143
                    if (!isset($this->jsonFile[self::SWAGGER_COMPONENTS][self::SWAGGER_PARAMETERS][$paramParts[3]])) {
144
                        throw new DefinitionNotFoundException(
145
                            "Not find reference #/components/parameters/${paramParts[3]}"
146
                        );
147
                    }
148
                    $parameter = $this->jsonFile[self::SWAGGER_COMPONENTS][self::SWAGGER_PARAMETERS][$paramParts[3]];
149
                }
150 View Code Duplication
                if ($parameter['in'] === $parameterIn &&
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...
151
                    $parameter['schema']['type'] === "integer"
152
                    && filter_var($arguments[$parameter['name']], FILTER_VALIDATE_INT) === false) {
153
                    throw new NotMatchedException('Path expected an integer value');
154
                }
155
            }
156
            return;
157
        }
158
159
        foreach ($parameters as $parameter) {
160 View Code Duplication
            if ($parameter['in'] === $parameterIn
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...
161
                && $parameter['type'] === "integer"
162
                && filter_var($arguments[$parameter['name']], FILTER_VALIDATE_INT) === false) {
163
                throw new NotMatchedException('Path expected an integer value');
164
            }
165
        }
166
    }
167
168
    /**
169
     * @param $name
170
     * @return mixed
171
     * @throws DefinitionNotFoundException
172
     * @throws InvalidDefinitionException
173
     */
174
    public function getDefintion($name)
175
    {
176
        $nameParts = explode('/', $name);
177
178
        if ($this->getSpecificationVersion() === '3') {
179 View Code Duplication
            if (count($nameParts) < 4 || $nameParts[0] !== '#') {
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...
180
                throw new InvalidDefinitionException('Invalid Component');
181
            }
182
183 View Code Duplication
            if (!isset($this->jsonFile[$nameParts[1]][$nameParts[2]][$nameParts[3]])) {
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...
184
                throw new DefinitionNotFoundException("Component'$name' not found");
185
            }
186
187
            return $this->jsonFile[$nameParts[1]][$nameParts[2]][$nameParts[3]];
188
        }
189
190 View Code Duplication
        if (count($nameParts) < 3 || $nameParts[0] !== '#') {
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...
191
            throw new InvalidDefinitionException('Invalid Definition');
192
        }
193
194 View Code Duplication
        if (!isset($this->jsonFile[$nameParts[1]][$nameParts[2]])) {
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...
195
            throw new DefinitionNotFoundException("Definition '$name' not found");
196
        }
197
198
        return $this->jsonFile[$nameParts[1]][$nameParts[2]];
199
    }
200
201
    /**
202
     * @param $path
203
     * @param $method
204
     * @return SwaggerRequestBody
205
     * @throws DefinitionNotFoundException
206
     * @throws HttpMethodNotFoundException
207
     * @throws InvalidDefinitionException
208
     * @throws NotMatchedException
209
     * @throws PathNotFoundException
210
     */
211
    public function getRequestParameters($path, $method)
212
    {
213
        $structure = $this->getPathDefinition($path, $method);
214
215
        if($this->getSpecificationVersion() === '3') {
216
            if (!isset($structure['requestBody'])) {
217
                return new SwaggerRequestBody($this, "$method $path", []);
218
            }
219
            return new SwaggerRequestBody($this, "$method $path", $structure['requestBody']);
220
        }
221
222
        if (!isset($structure[self::SWAGGER_PARAMETERS])) {
223
            return new SwaggerRequestBody($this, "$method $path", []);
224
        }
225
        return new SwaggerRequestBody($this, "$method $path", $structure[self::SWAGGER_PARAMETERS]);
226
    }
227
228
    /**
229
     * @param $path
230
     * @param $method
231
     * @param $status
232
     * @return SwaggerResponseBody
233
     * @throws HttpMethodNotFoundException
234
     * @throws InvalidDefinitionException
235
     * @throws NotMatchedException
236
     * @throws PathNotFoundException
237
     * @throws DefinitionNotFoundException
238
     */
239
    public function getResponseParameters($path, $method, $status)
240
    {
241
        $structure = $this->getPathDefinition($path, $method);
242
243
        if (!isset($structure['responses'][$status])) {
244
            throw new InvalidDefinitionException("Could not found status code '$status' in '$path' and '$method'");
245
        }
246
247
        return new SwaggerResponseBody($this, "$method $status $path", $structure['responses'][$status]);
248
    }
249
250
    /**
251
     * OpenApi 2.0 doesn't describe null values, so this flag defines,
252
     * if match is ok when one of property
253
     *
254
     * @return bool
255
     */
256
    public function isAllowNullValues()
257
    {
258
        return $this->allowNullValues;
259
    }
260
261
    /**
262
     * OpenApi 2.0 doesn't describe null values, so this flag defines,
263
     * if match is ok when one of property
264
     *
265
     * @param $value
266
     */
267
    public function setAllowNullValues($value)
268
    {
269
        $this->allowNullValues = (bool) $value;
270
    }
271
}
272