Passed
Push — master ( f9279d...87874e )
by Joao
01:35 queued 11s
created

Body   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 6
dl 0
loc 356
rs 8.48
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A getInstance() 0 12 3
match() 0 1 ?
A matchString() 0 12 4
A matchFile() 0 8 2
A matchNumber() 0 12 5
A matchBool() 0 12 4
A matchArray() 0 14 4
B matchTypes() 0 50 5
B matchObjectProperties() 0 54 10
B matchSchema() 0 34 7
A matchNull() 0 15 3

How to fix   Complexity   

Complex Class

Complex classes like Body 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 Body, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ByJG\ApiTools\Base;
4
5
use ByJG\ApiTools\Exception\DefinitionNotFoundException;
6
use ByJG\ApiTools\Exception\GenericSwaggerException;
7
use ByJG\ApiTools\Exception\InvalidDefinitionException;
8
use ByJG\ApiTools\Exception\InvalidRequestException;
9
use ByJG\ApiTools\Exception\NotMatchedException;
10
use ByJG\ApiTools\OpenApi\OpenApiResponseBody;
11
use ByJG\ApiTools\OpenApi\OpenApiSchema;
12
use ByJG\ApiTools\Swagger\SwaggerResponseBody;
13
use ByJG\ApiTools\Swagger\SwaggerSchema;
14
use InvalidArgumentException;
15
16
abstract class Body
17
{
18
    const SWAGGER_PROPERTIES="properties";
19
    const SWAGGER_REQUIRED="required";
20
21
    /**
22
     * @var Schema
23
     */
24
    protected $schema;
25
26
    protected $structure;
27
28
    protected $name;
29
30
    /**
31
     * OpenApi 2.0 does not describe null values, so this flag defines,
32
     * if match is ok when one of property, which has type, is null
33
     *
34
     * @var bool
35
     */
36
    protected $allowNullValues;
37
38
    /**
39
     * Body constructor.
40
     *
41
     * @param Schema $schema
42
     * @param string $name
43
     * @param array $structure
44
     * @param bool $allowNullValues
45
     */
46
    public function __construct(Schema $schema, $name, $structure, $allowNullValues = false)
47
    {
48
        $this->schema = $schema;
49
        $this->name = $name;
50
        if (!is_array($structure)) {
51
            throw new InvalidArgumentException('I expected the structure to be an array');
52
        }
53
        $this->structure = $structure;
54
        $this->allowNullValues = $allowNullValues;
55
    }
56
57
    /**
58
     * @param Schema $schema
59
     * @param $name
60
     * @param $structure
61
     * @param bool $allowNullValues
62
     * @return OpenApiResponseBody|SwaggerResponseBody
63
     * @throws GenericSwaggerException
64
     */
65
    public static function getInstance(Schema $schema, $name, $structure, $allowNullValues = false)
66
    {
67
        if ($schema instanceof SwaggerSchema) {
68
            return new SwaggerResponseBody($schema, $name, $structure, $allowNullValues);
69
        }
70
71
        if ($schema instanceof OpenApiSchema) {
72
            return new OpenApiResponseBody($schema, $name, $structure, $allowNullValues);
73
        }
74
75
        throw new GenericSwaggerException("Cannot get instance SwaggerBody or SchemaBody from " . get_class($schema));
76
    }
77
78
    abstract public function match($body);
79
80
    /**
81
     * @param $name
82
     * @param $schema
83
     * @param $body
84
     * @param $type
85
     * @return bool
86
     * @throws NotMatchedException
87
     */
88
    protected function matchString($name, $schema, $body, $type)
89
    {
90
        if ($type !== 'string') {
91
            return null;
92
        }
93
94
        if (isset($schema['enum']) && !in_array($body, $schema['enum'])) {
95
            throw new NotMatchedException("Value '$body' in '$name' not matched in ENUM. ", $this->structure);
96
        }
97
98
        return true;
99
    }
100
101
    /**
102
     * @param $name
103
     * @param $schema
104
     * @param $body
105
     * @param $type
106
     * @return bool
107
     * @throws NotMatchedException
108
     */
109
    protected function matchFile($name, $schema, $body, $type)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $schema is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $body is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
110
    {
111
        if ($type !== 'file') {
112
            return null;
113
        }
114
115
        return true;
116
    }
117
118
    /**
119
     * @param $name
120
     * @param $body
121
     * @param $type
122
     * @return bool
123
     * @throws NotMatchedException
124
     */
125
    protected function matchNumber($name, $body, $type)
126
    {
127
        if ($type !== 'integer' && $type !== 'float' && $type !== 'number') {
128
            return null;
129
        }
130
131
        if (!is_numeric($body)) {
132
            throw new NotMatchedException("Expected '$name' to be numeric, but found '$body'. ", $this->structure);
133
        }
134
135
        return true;
136
    }
137
138
    /**
139
     * @param $name
140
     * @param $body
141
     * @param $type
142
     * @return bool
143
     * @throws NotMatchedException
144
     */
145
    protected function matchBool($name, $body, $type)
146
    {
147
        if ($type !== 'bool' && $type !== 'boolean') {
148
            return null;
149
        }
150
151
        if (!is_bool($body)) {
152
            throw new NotMatchedException("Expected '$name' to be boolean, but found '$body'. ", $this->structure);
153
        }
154
155
        return true;
156
    }
157
158
    /**
159
     * @param $name
160
     * @param $schema
161
     * @param $body
162
     * @param $type
163
     * @return bool
164
     * @throws DefinitionNotFoundException
165
     * @throws GenericSwaggerException
166
     * @throws InvalidDefinitionException
167
     * @throws InvalidRequestException
168
     * @throws NotMatchedException
169
     */
170
    protected function matchArray($name, $schema, $body, $type)
171
    {
172
        if ($type !== 'array') {
173
            return null;
174
        }
175
176
        foreach ((array)$body as $item) {
177
            if (!isset($schema['items'])) {  // If there is no type , there is no test.
178
                continue;
179
            }
180
            $this->matchSchema($name, $schema['items'], $item);
181
        }
182
        return true;
183
    }
184
185
    protected function matchTypes($name, $schema, $body)
186
    {
187
        if (!isset($schema['type'])) {
188
            return null;
189
        }
190
191
        $type = $schema['type'];
192
        $nullable = isset($schema['nullable']) ? (bool)$schema['nullable'] : $this->schema->isAllowNullValues();
193
194
        $validators = [
195
            function () use ($name, $body, $type, $nullable)
196
            {
197
                return $this->matchNull($name, $body, $type, $nullable);
198
            },
199
200
            function () use ($name, $schema, $body, $type)
201
            {
202
                return $this->matchString($name, $schema, $body, $type);
203
            },
204
205
            function () use ($name, $body, $type)
206
            {
207
                return $this->matchNumber($name, $body, $type);
208
            },
209
210
            function () use ($name, $body, $type)
211
            {
212
                return $this->matchBool($name, $body, $type);
213
            },
214
215
            function () use ($name, $schema, $body, $type)
216
            {
217
                return $this->matchArray($name, $schema, $body, $type);
218
            },
219
220
            function () use ($name, $schema, $body, $type)
221
            {
222
                return $this->matchFile($name, $schema, $body, $type);
223
            },
224
        ];
225
226
        foreach ($validators as $validator) {
227
            $result = $validator();
228
            if (!is_null($result)) {
229
                return $result;
230
            }
231
        }
232
233
        return null;
234
    }
235
236
    /**
237
     * @param $name
238
     * @param $schema
239
     * @param $body
240
     * @return bool|null
241
     * @throws DefinitionNotFoundException
242
     * @throws GenericSwaggerException
243
     * @throws InvalidDefinitionException
244
     * @throws InvalidRequestException
245
     * @throws NotMatchedException
246
     */
247
    public function matchObjectProperties($name, $schema, $body)
248
    {
249
        if (!isset($schema[self::SWAGGER_PROPERTIES])) {
250
            return null;
251
        }
252
253
        if (!is_array($body)) {
254
            throw new InvalidRequestException(
255
                "I expected an array here, but I got an string. Maybe you did wrong request?",
256
                $body
257
            );
258
        }
259
260
        if (!isset($schema[self::SWAGGER_REQUIRED])) {
261
            $schema[self::SWAGGER_REQUIRED] = [];
262
        }
263
        foreach ($schema[self::SWAGGER_PROPERTIES] as $prop => $def) {
264
            $required = array_search($prop, $schema[self::SWAGGER_REQUIRED]);
265
266
            if (!array_key_exists($prop, $body)) {
267
                if ($required !== false) {
268
                    throw new NotMatchedException("Required property '$prop' in '$name' not found in object");
269
                }
270
                unset($body[$prop]);
271
                continue;
272
            }
273
274
            $this->matchSchema($prop, $def, $body[$prop]);
275
            unset($schema[self::SWAGGER_PROPERTIES][$prop]);
276
            if ($required !== false) {
277
                unset($schema[self::SWAGGER_REQUIRED][$required]);
278
            }
279
            unset($body[$prop]);
280
        }
281
282
        if (count($schema[self::SWAGGER_REQUIRED]) > 0) {
283
            throw new NotMatchedException(
284
                "The required property(ies) '"
285
                . implode(', ', $schema[self::SWAGGER_REQUIRED])
286
                . "' does not exists in the body.",
287
                $this->structure
288
            );
289
        }
290
291
        if (count($body) > 0) {
292
            throw new NotMatchedException(
293
                "The property(ies) '"
294
                . implode(', ', array_keys($body))
295
                . "' has not defined in '$name'",
296
                $body
297
            );
298
        }
299
        return true;
300
    }
301
302
    /**
303
     * @param string $name
304
     * @param $schema
305
     * @param array $body
306
     * @return bool
307
     * @throws DefinitionNotFoundException
308
     * @throws InvalidDefinitionException
309
     * @throws GenericSwaggerException
310
     * @throws InvalidRequestException
311
     * @throws NotMatchedException
312
     */
313
    protected function matchSchema($name, $schema, $body)
314
    {
315
        // Match Single Types
316
        if ($this->matchTypes($name, $schema, $body)) {
317
            return true;
318
        }
319
320
        if(!isset($schema['$ref']) && isset($schema['content'])) {
321
            $schema['$ref'] = $schema['content'][key($schema['content'])]['schema']['$ref'];
322
        }
323
324
        // Get References and try to match it again
325
        if (isset($schema['$ref'])) {
326
            $defintion = $this->schema->getDefinition($schema['$ref']);
327
            return $this->matchSchema($schema['$ref'], $defintion, $body);
328
        }
329
330
        // Match object properties
331
        if ($this->matchObjectProperties($name, $schema, $body)) {
332
            return true;
333
        }
334
335
        /**
336
         * OpenApi 2.0 does not describe ANY object value
337
         * But there is hack that makes ANY object possible, described in link below
338
         * To make that hack works, we need such condition
339
         * @link https://stackoverflow.com/questions/32841298/swagger-2-0-what-schema-to-accept-any-complex-json-value
340
         */
341
        if ($schema === []) {
342
            return true;
343
        }
344
345
        throw new GenericSwaggerException("Not all cases are defined. Please open an issue about this. Schema: $name");
346
    }
347
348
    /**
349
     * @param $name
350
     * @param $body
351
     * @param $type
352
     * @param $nullable
353
     * @return bool
354
     * @throws NotMatchedException
355
     */
356
    protected function matchNull($name, $body, $type, $nullable)
357
    {
358
        if (!is_null($body)) {
359
            return null;
360
        }
361
362
        if (!$nullable) {
363
            throw new NotMatchedException(
364
                "Value of property '$name' is null, but should be of type '$type'",
365
                $this->structure
366
            );
367
        }
368
369
        return true;
370
    }
371
}
372