Passed
Pull Request — master (#61)
by
unknown
02:28
created

Body::checkPattern()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 3
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
    /**
27
     * @var array
28
     */
29
    protected $structure;
30
31
    /**
32
     * @var string
33
     */
34
    protected $name;
35
36
    /**
37
     * OpenApi 2.0 does not describe null values, so this flag defines,
38
     * if match is ok when one of property, which has type, is null
39
     *
40
     * @var bool
41
     */
42
    protected $allowNullValues;
43
44
    /**
45
     * Body constructor.
46
     *
47
     * @param Schema $schema
48
     * @param string $name
49
     * @param array $structure
50
     * @param bool $allowNullValues
51
     */
52
    public function __construct(Schema $schema, $name, $structure, $allowNullValues = false)
53
    {
54
        $this->schema = $schema;
55
        $this->name = $name;
56
        if (!is_array($structure)) {
57
            throw new InvalidArgumentException('I expected the structure to be an array');
58
        }
59
        $this->structure = $structure;
60
        $this->allowNullValues = $allowNullValues;
61
    }
62
63
    /**
64
     * @param Schema $schema
65
     * @param string $name
66
     * @param array $structure
67
     * @param bool $allowNullValues
68
     * @return OpenApiResponseBody|SwaggerResponseBody
69
     * @throws GenericSwaggerException
70
     */
71
    public static function getInstance(Schema $schema, $name, $structure, $allowNullValues = false)
72
    {
73
        if ($schema instanceof SwaggerSchema) {
74
            return new SwaggerResponseBody($schema, $name, $structure, $allowNullValues);
75
        }
76
77
        if ($schema instanceof OpenApiSchema) {
78
            return new OpenApiResponseBody($schema, $name, $structure, $allowNullValues);
79
        }
80
81
        throw new GenericSwaggerException("Cannot get instance SwaggerBody or SchemaBody from " . get_class($schema));
82
    }
83
84
    abstract public function match($body);
85
86
    /**
87
     * @param string $name
88
     * @param array $schemaArray
89
     * @param string $body
90
     * @param string $type
91
     * @return bool
92
     * @throws NotMatchedException
93
     */
94
    protected function matchString($name, $schemaArray, $body, $type)
95
    {
96
        if ($type !== 'string') {
97
            return null;
98
        }
99
100
        if (isset($schemaArray['enum']) && !in_array($body, $schemaArray['enum'])) {
101
            throw new NotMatchedException("Value '$body' in '$name' not matched in ENUM. ", $this->structure);
102
        }
103
104
        if (isset($schemaArray['pattern'])) {
105
            $this->checkPattern($name, $body, $schemaArray['pattern']);
106
        }
107
108
        return true;
109
    }
110
111
    private function checkPattern($name, $body, $pattern)
112
    {
113
        $isSuccess = (bool) preg_match($pattern, $body, $matches);
114
115
        if (!$isSuccess) {
116
            throw new NotMatchedException("Value '$body' in '$name' not matched in pattern. ", $this->structure);
117
        }
118
    }
119
120
121
    /**
122
     * @param string $name
123
     * @param array $schemaArray
124
     * @param string $body
125
     * @param string $type
126
     * @return bool
127
     */
128
    protected function matchFile($name, $schemaArray, $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 $schemaArray 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...
129
    {
130
        if ($type !== 'file') {
131
            return null;
132
        }
133
134
        return true;
135
    }
136
137
    /**
138
     * @param string $name
139
     * @param string $body
140
     * @param string $type
141
     * @return bool
142
     * @throws NotMatchedException
143
     */
144
    protected function matchNumber($name, $body, $type)
145
    {
146
        if ($type !== 'integer' && $type !== 'float' && $type !== 'number') {
147
            return null;
148
        }
149
150
        if (!is_numeric($body)) {
151
            throw new NotMatchedException("Expected '$name' to be numeric, but found '$body'. ", $this->structure);
152
        }
153
154
        if (isset($schemaArray['pattern'])) {
0 ignored issues
show
Bug introduced by
The variable $schemaArray seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
155
            $this->checkPattern($name, $body, $schemaArray['pattern']);
156
        }
157
158
        return true;
159
    }
160
161
    /**
162
     * @param string $name
163
     * @param string $body
164
     * @param string $type
165
     * @return bool
166
     * @throws NotMatchedException
167
     */
168
    protected function matchBool($name, $body, $type)
169
    {
170
        if ($type !== 'bool' && $type !== 'boolean') {
171
            return null;
172
        }
173
174
        if (!is_bool($body)) {
175
            throw new NotMatchedException("Expected '$name' to be boolean, but found '$body'. ", $this->structure);
176
        }
177
178
        return true;
179
    }
180
181
    /**
182
     * @param string $name
183
     * @param array $schemaArray
184
     * @param string $body
185
     * @param string $type
186
     * @return bool
187
     * @throws DefinitionNotFoundException
188
     * @throws GenericSwaggerException
189
     * @throws InvalidDefinitionException
190
     * @throws InvalidRequestException
191
     * @throws NotMatchedException
192
     */
193
    protected function matchArray($name, $schemaArray, $body, $type)
194
    {
195
        if ($type !== 'array') {
196
            return null;
197
        }
198
199
        foreach ((array)$body as $item) {
200
            if (!isset($schemaArray['items'])) {  // If there is no type , there is no test.
201
                continue;
202
            }
203
            $this->matchSchema($name, $schemaArray['items'], $item);
204
        }
205
        return true;
206
    }
207
208
    /**
209
     * @param string $name
210
     * @param array $schemaArray
211
     * @param string $body
212
     * @return mixed|null
213
     */
214
    protected function matchTypes($name, $schemaArray, $body)
215
    {
216
        if (!isset($schemaArray['type'])) {
217
            return null;
218
        }
219
220
        $type = $schemaArray['type'];
221
        $nullable = isset($schemaArray['nullable']) ? (bool)$schemaArray['nullable'] : $this->schema->isAllowNullValues();
222
223
        $validators = [
224
            function () use ($name, $body, $type, $nullable)
225
            {
226
                return $this->matchNull($name, $body, $type, $nullable);
227
            },
228
229
            function () use ($name, $schemaArray, $body, $type)
230
            {
231
                return $this->matchString($name, $schemaArray, $body, $type);
232
            },
233
234
            function () use ($name, $body, $type)
235
            {
236
                return $this->matchNumber($name, $body, $type);
237
            },
238
239
            function () use ($name, $body, $type)
240
            {
241
                return $this->matchBool($name, $body, $type);
242
            },
243
244
            function () use ($name, $schemaArray, $body, $type)
245
            {
246
                return $this->matchArray($name, $schemaArray, $body, $type);
247
            },
248
249
            function () use ($name, $schemaArray, $body, $type)
250
            {
251
                return $this->matchFile($name, $schemaArray, $body, $type);
252
            },
253
        ];
254
255
        foreach ($validators as $validator) {
256
            $result = $validator();
257
            if (!is_null($result)) {
258
                return $result;
259
            }
260
        }
261
262
        return null;
263
    }
264
265
    /**
266
     * @param string $name
267
     * @param array $schemaArray
268
     * @param string $body
269
     * @return bool|null
270
     * @throws DefinitionNotFoundException
271
     * @throws GenericSwaggerException
272
     * @throws InvalidDefinitionException
273
     * @throws InvalidRequestException
274
     * @throws NotMatchedException
275
     */
276
    public function matchObjectProperties($name, $schemaArray, $body)
277
    {
278
        if (!isset($schemaArray[self::SWAGGER_PROPERTIES])) {
279
            return null;
280
        }
281
282
        if (!is_array($body)) {
283
            throw new InvalidRequestException(
284
                "I expected an array here, but I got an string. Maybe you did wrong request?",
285
                $body
0 ignored issues
show
Documentation introduced by
$body is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
286
            );
287
        }
288
289
        if (!isset($schemaArray[self::SWAGGER_REQUIRED])) {
290
            $schemaArray[self::SWAGGER_REQUIRED] = [];
291
        }
292
        foreach ($schemaArray[self::SWAGGER_PROPERTIES] as $prop => $def) {
293
            $required = array_search($prop, $schemaArray[self::SWAGGER_REQUIRED]);
294
295
            if (!array_key_exists($prop, $body)) {
296
                if ($required !== false) {
297
                    throw new NotMatchedException("Required property '$prop' in '$name' not found in object");
298
                }
299
                unset($body[$prop]);
300
                continue;
301
            }
302
303
            $this->matchSchema($prop, $def, $body[$prop]);
304
            unset($schemaArray[self::SWAGGER_PROPERTIES][$prop]);
305
            if ($required !== false) {
306
                unset($schemaArray[self::SWAGGER_REQUIRED][$required]);
307
            }
308
            unset($body[$prop]);
309
        }
310
311
        if (count($schemaArray[self::SWAGGER_REQUIRED]) > 0) {
312
            throw new NotMatchedException(
313
                "The required property(ies) '"
314
                . implode(', ', $schemaArray[self::SWAGGER_REQUIRED])
315
                . "' does not exists in the body.",
316
                $this->structure
317
            );
318
        }
319
320
        if (count($body) > 0) {
321
            throw new NotMatchedException(
322
                "The property(ies) '"
323
                . implode(', ', array_keys($body))
324
                . "' has not defined in '$name'",
325
                $body
326
            );
327
        }
328
        return true;
329
    }
330
331
    /**
332
     * @param string $name
333
     * @param array $schemaArray
334
     * @param array $body
335
     * @return bool
336
     * @throws DefinitionNotFoundException
337
     * @throws InvalidDefinitionException
338
     * @throws GenericSwaggerException
339
     * @throws InvalidRequestException
340
     * @throws NotMatchedException
341
     */
342
    protected function matchSchema($name, $schemaArray, $body)
343
    {
344
        // Match Single Types
345
        if ($this->matchTypes($name, $schemaArray, $body)) {
0 ignored issues
show
Documentation introduced by
$body is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
346
            return true;
347
        }
348
349
        if(!isset($schemaArray['$ref']) && isset($schemaArray['content'])) {
350
            $schemaArray['$ref'] = $schemaArray['content'][key($schemaArray['content'])]['schema']['$ref'];
351
        }
352
353
        // Get References and try to match it again
354
        if (isset($schemaArray['$ref'])) {
355
            $defintion = $this->schema->getDefinition($schemaArray['$ref']);
356
            return $this->matchSchema($schemaArray['$ref'], $defintion, $body);
357
        }
358
359
        // Match object properties
360
        if ($this->matchObjectProperties($name, $schemaArray, $body)) {
0 ignored issues
show
Documentation introduced by
$body is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
361
            return true;
362
        }
363
364
        /**
365
         * OpenApi 2.0 does not describe ANY object value
366
         * But there is hack that makes ANY object possible, described in link below
367
         * To make that hack works, we need such condition
368
         * @link https://stackoverflow.com/questions/32841298/swagger-2-0-what-schema-to-accept-any-complex-json-value
369
         */
370
        if ($schemaArray === []) {
371
            return true;
372
        }
373
374
        throw new GenericSwaggerException("Not all cases are defined. Please open an issue about this. Schema: $name");
375
    }
376
377
    /**
378
     * @param string $name
379
     * @param string $body
380
     * @param string $type
381
     * @param bool $nullable
382
     * @return bool
383
     * @throws NotMatchedException
384
     */
385
    protected function matchNull($name, $body, $type, $nullable)
386
    {
387
        if (!is_null($body)) {
388
            return null;
389
        }
390
391
        if (!$nullable) {
392
            throw new NotMatchedException(
393
                "Value of property '$name' is null, but should be of type '$type'",
394
                $this->structure
395
            );
396
        }
397
398
        return true;
399
    }
400
}
401