Schema::processNumeric()   F
last analyzed

Complexity

Conditions 17
Paths 675

Size

Total Lines 53
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 17

Importance

Changes 0
Metric Value
eloc 36
c 0
b 0
f 0
dl 0
loc 53
ccs 26
cts 26
cp 1
rs 1.5013
cc 17
nop 2
crap 17
nc 675

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Swaggest\JsonSchema;
4
5
6
use PhpLang\ScopeExit;
7
use Swaggest\JsonDiff\JsonDiff;
8
use Swaggest\JsonDiff\JsonPointer;
9
use Swaggest\JsonSchema\Constraint\Content;
10
use Swaggest\JsonSchema\Constraint\Format;
11
use Swaggest\JsonSchema\Constraint\Properties;
12
use Swaggest\JsonSchema\Constraint\Type;
13
use Swaggest\JsonSchema\Constraint\UniqueItems;
14
use Swaggest\JsonSchema\Exception\ArrayException;
15
use Swaggest\JsonSchema\Exception\ConstException;
16
use Swaggest\JsonSchema\Exception\EnumException;
17
use Swaggest\JsonSchema\Exception\LogicException;
18
use Swaggest\JsonSchema\Exception\NumericException;
19
use Swaggest\JsonSchema\Exception\ObjectException;
20
use Swaggest\JsonSchema\Exception\StringException;
21
use Swaggest\JsonSchema\Exception\TypeException;
22
use Swaggest\JsonSchema\Meta\MetaHolder;
23
use Swaggest\JsonSchema\Path\PointerUtil;
24
use Swaggest\JsonSchema\Structure\ClassStructure;
25
use Swaggest\JsonSchema\Structure\Egg;
26
use Swaggest\JsonSchema\Structure\ObjectItem;
27
use Swaggest\JsonSchema\Structure\ObjectItemContract;
28
use Swaggest\JsonSchema\Structure\WithResolvedValue;
29
30
/**
31
 * Class Schema
32
 * @package Swaggest\JsonSchema
33
 */
34
class Schema extends JsonSchema implements MetaHolder, SchemaContract, HasDefault
35
{
36
    const ENUM_NAMES_PROPERTY = 'x-enum-names';
37
    const CONST_PROPERTY = 'const';
38
    const DEFAULT_PROPERTY = 'default';
39
40
    const DEFAULT_MAPPING = 'default';
41
42
    const VERSION_AUTO = 'a';
43
    const VERSION_DRAFT_04 = 4;
44
    const VERSION_DRAFT_06 = 6;
45
    const VERSION_DRAFT_07 = 7;
46
47
    const PROP_REF = '$ref';
48
    const PROP_ID = '$id';
49
    const PROP_ID_D4 = 'id';
50
51
    // Object
52
    /** @var null|Properties */
53
    public $properties;
54
    /** @var SchemaContract|bool */
55
    public $additionalProperties;
56
    /** @var SchemaContract[]|Properties */
57
    public $patternProperties;
58
    /** @var string[][]|Schema[]|\stdClass */
59
    public $dependencies;
60
61
    // Array
62
    /** @var null|SchemaContract|SchemaContract[] */
63
    public $items;
64
    /** @var null|SchemaContract|bool */
65
    public $additionalItems;
66
67
    /** @var SchemaContract[] */
68
    public $allOf;
69
    /** @var SchemaContract */
70
    public $not;
71
    /** @var SchemaContract[] */
72
    public $anyOf;
73
    /** @var SchemaContract[] */
74
    public $oneOf;
75
76
    /** @var SchemaContract */
77
    public $if;
78
    /** @var SchemaContract */
79
    public $then;
80
    /** @var SchemaContract */
81
    public $else;
82
83
84
    public $objectItemClass;
85
86
    /**
87
     * @todo check usages/deprecate
88
     * @var bool
89
     */
90
    private $useObjectAsArray = false;
91
92 4
    private $__booleanSchema;
93
94 4
    public function addPropertyMapping($dataName, $propertyName, $mapping = self::DEFAULT_MAPPING)
95
    {
96
        if (null === $this->properties) {
97
            $this->properties = new Properties();
98 4
        }
99 4
100
        $this->properties->addPropertyMapping($dataName, $propertyName, $mapping);
101
        return $this;
102
    }
103
104
    /**
105
     * @param mixed $data
106
     * @param Context|null $options
107
     * @return SchemaContract
108
     * @throws Exception
109
     * @throws InvalidValue
110 3298
     * @throws \Exception
111
     */
112 3298
    public static function import($data, Context $options = null)
113 33
    {
114
        if (null === $options) {
115
            $options = new Context();
116 3298
        }
117
118 3298
        $options->applyDefaults = false;
119
120
        if (isset($options->schemasCache) && is_object($data)) {
121
            if ($options->schemasCache->contains($data)) {
122
                return $options->schemasCache->offsetGet($data);
123
            } else {
124
                $schema = parent::import($data, $options);
125
                $options->schemasCache->attach($data, $schema);
126
                return $schema;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $schema also could return the type Swaggest\JsonSchema\Structure\ClassStructureTrait which is incompatible with the documented return type Swaggest\JsonSchema\SchemaContract.
Loading history...
127
            }
128
        }
129 3298
130 6
        // string $data is expected to be $ref uri
131
        if (is_string($data)) {
132
            $data = (object)array(self::PROP_REF => $data);
133 3298
        }
134 3298
135 72
        $data = self::unboolSchema($data);
136
        if ($data instanceof SchemaContract) {
137
            return $data;
138 3226
        }
139
140
        return parent::import($data, $options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::import($data, $options) also could return the type Swaggest\JsonSchema\Structure\ClassStructureTrait which is incompatible with the documented return type Swaggest\JsonSchema\SchemaContract.
Loading history...
141
    }
142
143
    /**
144
     * @param mixed $data
145
     * @param Context|null $options
146
     * @return array|mixed|null|object|\stdClass
147
     * @throws Exception
148
     * @throws InvalidValue
149 3323
     * @throws \Exception
150
     */
151 3323
    public function in($data, Context $options = null)
152 72
    {
153 36
        if (null !== $this->__booleanSchema) {
154 36
            if ($this->__booleanSchema) {
155 18
                return $data;
156
            } elseif (empty($options->skipValidation)) {
157
                $this->fail(new InvalidValue('Denied by false schema'), '#');
158
            }
159 3269
        }
160 69
161
        if ($options === null) {
162
            $options = new Context();
163 3269
        }
164
165 3269
        $options->import = true;
166 2936
167
        if ($options->refResolver === null) {
168 1766
            $options->refResolver = new RefResolver($data);
169
        } else {
170
            $options->refResolver->setRootData($data);
171 3269
        }
172 3191
173
        if ($options->remoteRefProvider) {
174
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
175 3269
        }
176
177 3269
        $options->refResolver->preProcessReferences($data, $options);
178
179
        return $this->process($data, $options, '#');
180
    }
181
182
183
    /**
184
     * @param mixed $data
185
     * @param Context|null $options
186
     * @return array|mixed|null|object|\stdClass
187
     * @throws InvalidValue
188 2452
     * @throws \Exception
189
     */
190 2452
    public function out($data, Context $options = null)
191 1007
    {
192
        if ($options === null) {
193
            $options = new Context();
194 2452
        }
195 2452
196 2452
        $options->circularReferences = new \SplObjectStorage();
197
        $options->import = false;
198
        return $this->process($data, $options);
199
    }
200
201
    /**
202
     * @param mixed $data
203
     * @param Context $options
204
     * @param string $path
205
     * @throws InvalidValue
206 3257
     * @throws \Exception
207
     */
208 3257
    private function processType(&$data, Context $options, $path = '#')
209
    {
210
        if ($options->tolerateStrings && is_string($data)) {
211 3257
            $valid = Type::readString($this->type, $data);
212
        } else {
213 3257
            $valid = Type::isValid($this->type, $data, $options->version);
214 696
        }
215 696
        if (!$valid) {
216 696
            $this->fail(new TypeException(ucfirst(
217 696
                    implode(', ', is_array($this->type) ? $this->type : array($this->type))
218
                    . ' expected, ' . json_encode($data) . ' received')
219 3255
            ), $path);
220
        }
221
    }
222
223
    /**
224
     * @param mixed $data
225
     * @param string $path
226
     * @throws InvalidValue
227 1513
     * @throws \Exception
228
     */
229 1513
    private function processEnum($data, $path = '#')
230 1513
    {
231 1513
        $enumOk = false;
232 1493
        foreach ($this->enum as $item) {
233 1493
            if ($item === $data ||
234
                ( // Int and float equality check.
235 1449
                    (is_int($item) || is_float($item)) &&
236 12
                    (is_int($data) || is_float($data)) &&
237 12
                    $item == $data
238 3
                )
239 3
            ) {
240
                $enumOk = true;
241
                break;
242
            } else {
243
                if (is_array($item) || is_object($item)) {
244 1513
                    $diff = new JsonDiff($item, $data, JsonDiff::STOP_ON_DIFF);
245 107
                    if ($diff->getDiffCnt() === 0) {
246
                        $enumOk = true;
247 1496
                        break;
248
                    }
249
                }
250
            }
251
        }
252
        if (!$enumOk) {
253
            $this->fail(new EnumException('Enum failed, enum: ' . json_encode($this->enum) . ', data: ' . json_encode($data)), $path);
254
        }
255 43
    }
256
257 43
    /**
258 37
     * @param mixed $data
259 37
     * @param string $path
260 15
     * @throws InvalidValue
261 15
     * @throws \Swaggest\JsonDiff\Exception
262 15
     */
263 15
    private function processConst($data, $path)
264
    {
265
        if ($this->const !== $data &&
266 22
            !( // Int and float equality.
267
                (is_int($this->const) || is_float($this->const)) &&
268
                (is_int($data) || is_float($data)) &&
269 20
                $this->const == $data
270
            )
271
        ) {
272
            if ((is_object($this->const) && is_object($data))
273
                || (is_array($this->const) && is_array($data))) {
274
                $diff = new JsonDiff($this->const, $data,
275
                    JsonDiff::STOP_ON_DIFF);
276
                if ($diff->getDiffCnt() != 0) {
277
                    $this->fail(new ConstException('Const failed'), $path);
278
                }
279 70
            } else {
280
                $this->fail(new ConstException('Const failed'), $path);
281 70
            }
282
        }
283 70
    }
284 16
285
    /**
286
     * @param mixed $data
287 70
     * @param Context $options
288 56
     * @param string $path
289
     * @throws InvalidValue
290 16
     * @throws \Exception
291
     * @throws \Swaggest\JsonDiff\Exception
292
     */
293
    private function processNot($data, Context $options, $path)
294
    {
295
        $exception = false;
296
        try {
297 2331
            self::unboolSchema($this->not)->process($data, $options, $path . '->not');
298
        } catch (InvalidValue $exception) {
299 2331
            // Expected exception
300 38
        }
301 9
        if ($exception === false) {
302
            $this->fail(new LogicException('Not ' . json_encode($this->not) . ' expected, ' . json_encode($data) . ' received'), $path . '->not');
303
        }
304 2325
    }
305 43
306 19
    /**
307
     * @param string $data
308
     * @param string $path
309 2322
     * @throws InvalidValue
310 18
     */
311 4
    private function processString($data, $path)
312 4
    {
313
        if ($this->minLength !== null) {
314
            if (mb_strlen($data, 'UTF-8') < $this->minLength) {
315 2322
                $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
316 445
            }
317 445
        }
318 150
        if ($this->maxLength !== null) {
319 131
            if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
320
                $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
321
            }
322
        }
323 2322
        if ($this->pattern !== null) {
324
            if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
325
                $this->fail(new StringException(json_encode($data) . ' does not match to '
326
                    . $this->pattern, StringException::PATTERN_MISMATCH), $path);
327
            }
328
        }
329
        if ($this->format !== null) {
330 1090
            $validationError = Format::validationError($this->format, $data);
331
            if ($validationError !== null) {
332 1090
                if (!($this->format === "uri" && substr($path, -3) === ':id')) {
333 39
                    $this->fail(new StringException($validationError), $path);
334 39
                }
335 15
            }
336
        }
337
    }
338
339 1090
    /**
340 32
     * @param float|int $data
341 18
     * @param string $path
342 18
     * @throws InvalidValue
343 18
     */
344
    private function processNumeric($data, $path)
345
    {
346
        if ($this->multipleOf !== null) {
347 1090
            $div = $data / $this->multipleOf;
348 24
            if ($div != (int)$div) {
349 12
                $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
350 12
            }
351 12
        }
352
353
        if ($this->exclusiveMaximum !== null && !is_bool($this->exclusiveMaximum)) {
354
            if ($data >= $this->exclusiveMaximum) {
355 1090
                $this->fail(new NumericException(
356 46
                    'Value less or equal than ' . $this->exclusiveMaximum . ' expected, ' . $data . ' received',
357 3
                    NumericException::MAXIMUM), $path);
358 2
            }
359 2
        }
360 3
361
        if ($this->exclusiveMinimum !== null && !is_bool($this->exclusiveMinimum)) {
362
            if ($data <= $this->exclusiveMinimum) {
363 43
                $this->fail(new NumericException(
364 14
                    'Value more or equal than ' . $this->exclusiveMinimum . ' expected, ' . $data . ' received',
365 14
                    NumericException::MINIMUM), $path);
366 14
            }
367
        }
368
369
        if ($this->maximum !== null) {
370
            if ($this->exclusiveMaximum === true) {
371 1090
                if ($data >= $this->maximum) {
372 524
                    $this->fail(new NumericException(
373 93
                        'Value less or equal than ' . $this->maximum . ' expected, ' . $data . ' received',
374 2
                        NumericException::MAXIMUM), $path);
375 2
                }
376 93
            } else {
377
                if ($data > $this->maximum) {
378
                    $this->fail(new NumericException(
379 449
                        'Value less than ' . $this->maximum . ' expected, ' . $data . ' received',
380 46
                        NumericException::MAXIMUM), $path);
381 46
                }
382 46
            }
383
        }
384
385
        if ($this->minimum !== null) {
386 1089
            if ($this->exclusiveMinimum === true) {
387
                if ($data <= $this->minimum) {
388
                    $this->fail(new NumericException(
389
                        'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
390
                        NumericException::MINIMUM), $path);
391
                }
392
            } else {
393
                if ($data < $this->minimum) {
394
                    $this->fail(new NumericException(
395
                        'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
396
                        NumericException::MINIMUM), $path);
397 107
                }
398
            }
399 107
        }
400 107
    }
401 107
402 107
    /**
403 107
     * @param mixed $data
404 41
     * @param Context $options
405 41
     * @param string $path
406
     * @return array|mixed|null|object|\stdClass
407
     * @throws InvalidValue
408 107
     * @throws \Exception
409 107
     * @throws \Swaggest\JsonDiff\Exception
410
     */
411 107
    private function processOneOf($data, Context $options, $path)
412 85
    {
413 85
        $successes = 0;
414 85
        $failures = '';
415
        $subErrors = [];
416 75
        $skipValidation = false;
417 75
        if ($options->skipValidation) {
418 75
            $skipValidation = true;
419
            $options->skipValidation = false;
420
        }
421
422 107
        $result = $data;
423 41
        foreach ($this->oneOf as $index => $item) {
424 41
            try {
425 8
                $result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf[' . $index . ']');
426
                $successes++;
427
                if ($successes > 1 || $options->skipValidation) { // @phpstan-ignore-line
428
                    break;
429 107
                }
430 66
            } catch (InvalidValue $exception) {
431 20
                $subErrors[$index] = $exception;
432 20
                $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
433 20
                // Expected exception
434 20
            }
435 52
        }
436 17
        if ($skipValidation) {
437 17
            $options->skipValidation = true;
438 17
            if ($successes === 0) {
439 17
                $result = self::unboolSchema($this->oneOf[0])->process($data, $options, $path . '->oneOf[0]');
440 17
            }
441 17
        }
442
443
        if (!$options->skipValidation) {
444 77
            if ($successes === 0) {
445
                $exception = new LogicException('No valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}");
446
                $exception->error = 'No valid results for oneOf';
447
                $exception->subErrors = $subErrors;
448
                $this->fail($exception, $path);
449
            } elseif ($successes > 1) {
450
                $exception = new LogicException('More than 1 valid result for oneOf: '
451
                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf {'
452
                    . "\n" . substr($failures, 0, -1) . "\n}");
453
                $exception->error = 'More than 1 valid result for oneOf';
454
                $exception->subErrors = $subErrors;
455
                $this->fail($exception, $path);
456 1763
            }
457
        }
458 1763
        return $result;
459 1763
    }
460 1763
461 1763
    /**
462 1763
     * @param mixed $data
463
     * @param Context $options
464 1763
     * @param string $path
465 1757
     * @return array|mixed|null|object|\stdClass
466 1757
     * @throws InvalidValue
467 1757
     * @throws \Exception
468
     * @throws \Swaggest\JsonDiff\Exception
469 445
     */
470 445
    private function processAnyOf($data, Context $options, $path)
471 445
    {
472
        $successes = 0;
473
        $failures = '';
474
        $subErrors = [];
475 1763
        $result = $data;
476 31
        foreach ($this->anyOf as $index => $item) {
477 31
            try {
478 31
                $result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf[' . $index . ']');
479 31
                $successes++;
480 31
                if ($successes) {
481 31
                    break;
482
                }
483 1757
            } catch (InvalidValue $exception) {
484
                $subErrors[$index] = $exception;
485
                $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
486
                // Expected exception
487
            }
488
        }
489
        if (!$successes && !$options->skipValidation) {
490
            $exception = new LogicException('No valid results for anyOf {' . "\n"
491
                . substr(Helper::padLines(' ', $failures, false), 0, -1)
492
                . "\n}");
493
            $exception->error = 'No valid results for anyOf';
494
            $exception->subErrors = $subErrors;
495 354
            $this->fail($exception, $path);
496
        }
497 354
        return $result;
498 354
    }
499 354
500
    /**
501 314
     * @param mixed $data
502
     * @param Context $options
503
     * @param string $path
504
     * @return array|mixed|null|object|\stdClass
505
     * @throws InvalidValue
506
     * @throws \Exception
507
     * @throws \Swaggest\JsonDiff\Exception
508
     */
509
    private function processAllOf($data, Context $options, $path)
510
    {
511
        $result = $data;
512
        foreach ($this->allOf as $index => $item) {
513 26
            $result = self::unboolSchema($item)->process($data, $options, $path . '->allOf[' . $index . ']');
514
        }
515 26
        return $result;
516
    }
517 26
518 13
    /**
519 13
     * @param mixed $data
520
     * @param Context $options
521 26
     * @param string $path
522 18
     * @return array|mixed|null|object|\stdClass
523 18
     * @throws InvalidValue
524
     * @throws \Exception
525
     * @throws \Swaggest\JsonDiff\Exception
526 13
     */
527 6
    private function processIf($data, Context $options, $path)
528
    {
529
        $valid = true;
530 10
        try {
531
            self::unboolSchema($this->if)->process($data, $options, $path . '->if');
532
        } catch (InvalidValue $exception) {
533
            $valid = false;
534
        }
535
        if ($valid) {
536
            if ($this->then !== null) {
537
                return self::unboolSchema($this->then)->process($data, $options, $path . '->then');
538
            }
539 168
        } else {
540
            if ($this->else !== null) {
541 168
                return self::unboolSchema($this->else)->process($data, $options, $path . '->else');
542 164
            }
543 74
        }
544
        return null;
545
    }
546 136
547
    /**
548
     * @param array $array
549
     * @param Context $options
550
     * @param string $path
551
     * @throws InvalidValue
552
     */
553
    private function processObjectRequired($array, Context $options, $path)
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

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

553
    private function processObjectRequired($array, /** @scrutinizer ignore-unused */ Context $options, $path)

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

Loading history...
554
    {
555
        foreach ($this->required as $item) {
556
            if (!array_key_exists($item, $array)) {
557
                $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($array, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
558 3269
            }
559
        }
560 3269
    }
561
562 3269
    /**
563
     * @param object $data
564 3269
     * @param Context $options
565
     * @param string $path
566
     * @param ObjectItemContract|null $result
567 3269
     * @return array|null|ClassStructure|ObjectItemContract|SchemaContract
568 2
     * @throws InvalidValue
569 2
     * @throws \Exception
570 2
     * @throws \Swaggest\JsonDiff\Exception
571
     */
572
    private function processObject($data, Context $options, $path, $result = null)
573
    {
574 2
        $import = $options->import;
575 2
576 2
        $hasMapping = $this->properties !== null && isset($this->properties->__dataToProperty[$options->mapping]);
577 2
578 2
        $array = !$data instanceof \stdClass ? get_object_vars($data) : (array)$data;
579 2
580
        // convert imported data to default mapping before validation
581
        if ($import && $options->mapping !== self::DEFAULT_MAPPING) {
582
            if ($this->properties !== null && isset($this->properties->__dataToProperty[$options->mapping])) {
583
                foreach ($this->properties->__dataToProperty[$options->mapping] as $dataName => $propertyName) {
584
                    if (!isset($array[$dataName])) {
585 3269
                        continue;
586 168
                    }
587
588
                    $propertyName = isset($this->properties->__propertyToData[self::DEFAULT_MAPPING][$propertyName])
589
                        ? $this->properties->__propertyToData[self::DEFAULT_MAPPING][$propertyName]
590 3268
                        : $propertyName;
591 3251
                    if ($propertyName !== $dataName) {
592
                        $array[$propertyName] = $array[$dataName];
593 3251
                        unset($array[$dataName]);
594 1
                    }
595 3250
                }
596
            }
597 3250
        }
598 1186
599
        if (!$options->skipValidation && $this->required !== null) {
600 3239
            $this->processObjectRequired($array, $options, $path);
601 3239
        }
602
603
        // build result entity
604
        if ($import) {
605
            if (!$options->validateOnly) {
606 3239
607
                if ($this->useObjectAsArray) {
608
                    $result = array();
609
                } else {
610
                    //* todo check performance impact
611 3250
                    if (null === $this->objectItemClass) {
612 3235
                        if (!$result instanceof ObjectItemContract) {
613 3235
                            $result = new ObjectItem();
614
                            $result->setDocumentPath($path);
615
                        }
616
                    } else {
617
                        $className = $this->objectItemClass;
618
                        if ($options->objectItemClassMapping !== null) {
619
                            if (isset($options->objectItemClassMapping[$className])) {
620
                                $className = $options->objectItemClassMapping[$className];
621
                            }
622
                        }
623
                        if (null === $result || get_class($result) !== $className) {
624 3250
                            $result = new $className;
625 3250
                            //* todo check performance impact
626
                            if ($result instanceof ClassStructure) {
627
                                $result->setDocumentPath($path);
628
                                if ($result->__validateOnSet) {
629
                                    $result->__validateOnSet = false;
630
                                    /** @noinspection PhpUnusedLocalVariableInspection */
631
                                    /* todo check performance impact
632
                                    $validateOnSetHandler = new ScopeExit(function () use ($result) {
633
                                        $result->__validateOnSet = true;
634 3268
                                    });
635 3268
                                    //*/
636 3268
                                }
637 3268
                            }
638 42
                            //*/
639 42
                        }
640 42
                    }
641
                    //*/
642
                }
643 42
            }
644 42
        }
645
646
        // @todo better check for schema id
647 3268
648 3268
        if ($import
649 3268
            && isset($array[Schema::PROP_ID_D4])
650 3268
            && ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO)
651 116
            && is_string($array[Schema::PROP_ID_D4])) {
652 116
            $id = $array[Schema::PROP_ID_D4];
653 116
            $refResolver = $options->refResolver;
654
            $parentScope = $refResolver->updateResolutionScope($id);
655
            /** @noinspection PhpUnusedLocalVariableInspection */
656 116
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
0 ignored issues
show
Unused Code introduced by
The assignment to $defer is dead and can be removed.
Loading history...
657 116
                $refResolver->setResolutionScope($parentScope);
658
            });
659
        }
660
661 3268
        if ($import
662
            && isset($array[self::PROP_ID])
663
            && ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO)
664 3251
            && is_string($array[self::PROP_ID])) {
665 3251
            $id = $array[self::PROP_ID];
666
            $refResolver = $options->refResolver;
667 3251
            $parentScope = $refResolver->updateResolutionScope($id);
668 450
            /** @noinspection PhpUnusedLocalVariableInspection */
669
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
670 450
                $refResolver->setResolutionScope($parentScope);
671 14
            });
672
        }
673
674
        // check $ref
675
        if ($import) {
676 3251
            try {
677 3251
678 3251
                $refProperty = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $refProperty is dead and can be removed.
Loading history...
679
                $dereference = $options->dereference;
680 438
681
                if ($this->properties !== null && isset($array[self::PROP_REF])) {
682
                    $refPropName = self::PROP_REF;
683 438
                    if ($hasMapping) {
684 428
                        if (isset($this->properties->__dataToProperty[$options->mapping][self::PROP_REF])) {
685 438
                            $refPropName = $this->properties->__dataToProperty[$options->mapping][self::PROP_REF];
686 26
                        }
687
                    }
688
689
                    $refProperty = $this->properties[$refPropName];
690 412
691 412
                    if (isset($refProperty)) {
692
                        $dereference = $refProperty->format === Format::URI_REFERENCE;
693
                    }
694 412
                }
695 412
696
                if (
697 412
                    isset($array[self::PROP_REF])
698 412
                    && is_string($array[self::PROP_REF])
699 412
                    && $dereference
700 412
                ) {
701 187
                    $refString = $array[self::PROP_REF];
702 187
703
                    // todo check performance impact
704 412
                    if ($refString === 'http://json-schema.org/draft-04/schema#'
705
                        || $refString === 'http://json-schema.org/draft-06/schema#'
706 412
                        || $refString === 'http://json-schema.org/draft-07/schema#') {
707 412
                        return Schema::schema();
708 412
                    }
709 47
710
                    // TODO consider process # by reference here ?
711 412
                    $refResolver = $options->refResolver;
712
                    $preRefScope = $refResolver->getResolutionScope();
713 412
                    /** @noinspection PhpUnusedLocalVariableInspection */
714 1
                    $deferRefScope = new ScopeExit(function () use ($preRefScope, $refResolver) {
0 ignored issues
show
Unused Code introduced by
The assignment to $deferRefScope is dead and can be removed.
Loading history...
715 1
                        $refResolver->setResolutionScope($preRefScope);
716 1
                    });
717
718 412
                    $ref = $refResolver->resolveReference($refString);
719
                    $unresolvedData = $data;
720 3251
                    $data = self::unboolSchemaData($ref->getData());
721
                    if (!$options->validateOnly) {
722
                        if ($ref->isImported()) {
723 1
                            $refResult = $ref->getImported();
724 1
                            return $refResult;
725
                        }
726
                        $ref->setImported($result);
727
                        try {
728
                            // Best effort dereference delivery.
729 3268
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString, $result);
730
                            if ($refResult instanceof ObjectItemContract) {
731 3268
                                if ($refResult->getFromRefs()) {
732 3268
                                    $refResult = clone $refResult; // @todo check performance, consider option
733 3254
                                }
734
                                $refResult->setFromRef($refString);
735 3254
                            }
736 3254
                            $ref->setImported($refResult);
737
                            return $refResult;
738 590
                        } catch (InvalidValue $exception) {
739
                            if ($this->objectItemClass === 'Swaggest\JsonSchema\Schema' || $exception instanceof InvalidRef) {
740
                                throw $exception;
741
                            }
742
743 3268
                            $ref->unsetImported();
744 3247
                            $skipValidation = $options->skipValidation;
745 4
                            $options->skipValidation = true;
746
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString);
747 3247
                            if ($refResult instanceof ObjectItemContract) {
748 3
                                if ($refResult->getFromRefs()) {
749
                                    $refResult = clone $refResult; // @todo check performance, consider option
750 3247
                                }
751 19
                                $refResult->setFromRef($refString);
752 19
                            }
753 12
                            $options->skipValidation = $skipValidation;
754
755
                            if ($result instanceof WithResolvedValue) {
756
                                $result->setResolvedValue($refResult);
757
                            }
758 3268
759 3268
                            // Proceeding with unresolved data.
760 3268
                            $data = $unresolvedData;
761 3268
                        }
762 3268
                    } else {
763
                        $this->process($data, $options, $path . '->$ref:' . $refString);
764 43
                    }
765
                }
766 41
            } catch (InvalidValue $exception) {
767 6
                $this->fail($exception, $path);
768 6
            }
769 6
        }
770
771
        /** @var Schema[]|null $properties */
772
        $properties = null;
773
774
        $nestedProperties = null;
775 3268
        if ($this->properties !== null) {
776 3258
            $properties = $this->properties->toArray(); // todo call directly
777 1
            $nestedProperties = $this->properties->nestedProperties;
778
        }
779
780 3257
781
        if (!$options->skipValidation) {
782 3257
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
783 73
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
784 73
            }
785 63
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
786 63
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
787 63
            }
788 29
            if ($this->propertyNames !== null) {
789
                $propertyNames = self::unboolSchema($this->propertyNames);
790 34
                foreach ($array as $key => $tmp) {
791 31
                    $propertyNames->process($key, $options, $path . '->propertyNames:' . $key);
0 ignored issues
show
introduced by
The method process() does not exist on Swaggest\JsonSchema\JsonSchema. Maybe you want to declare this class abstract? ( Ignorable by Annotation )

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

791
                    $propertyNames->/** @scrutinizer ignore-call */ 
792
                                    process($key, $options, $path . '->propertyNames:' . $key);
Loading history...
792 18
                }
793 18
            }
794
        }
795
796
        $defaultApplied = array();
797
        if ($import
798
            && !$options->validateOnly
799
            && $options->applyDefaults
800 3257
            && $properties !== null
801 3257
        ) {
802
            foreach ($properties as $key => $property) {
803 3247
                $allowNull = false;
804 3247
                if ($property instanceof HasDefault) {
805 3247
                    if (!$property->hasDefault()) {
806 3247
                        continue;
807 3190
                    }
808 3190
809 3190
                    $allowNull = true;
810 3190
                }
811
812
                // todo check when property is \stdClass `{}` here (RefTest)
813
                if ($property instanceof SchemaContract) {
814
                    if (!array_key_exists($key, $array)) {
815
                        $default = $property->getDefault();
816 3252
                        if (null === $default && !$allowNull) { // @phpstan-ignore-line
817 3252
                            continue;
818 6
                        }
819 6
820
                        $defaultApplied[$key] = true;
821 6
                        $array[$key] = $default;
822
                    }
823
                }
824 3252
            }
825 178
        }
826 178
827 126
        /**
828 126
         * @var string $key
829 126
         * @var mixed $value
830 99
         */
831 99
        foreach ($array as $key => $value) {
832
            if ($key === '' && PHP_VERSION_ID < 70100) {
833
                $this->fail(new InvalidValue('Empty property name'), $path);
834
            }
835
836
            $found = false;
837 3252
838 1023
            if (!$options->skipValidation && !empty($this->dependencies)) {
839 15
                $deps = $this->dependencies;
840
                if (isset($deps->$key)) {
841
                    $dependencies = $deps->$key;
842 1023
                    $dependencies = self::unboolSchema($dependencies);
843 1023
                    if ($dependencies instanceof SchemaContract) {
844
                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
845
                    } else {
846 1016
                        foreach ($dependencies as $item) {
847 1012
                            if (!array_key_exists($item, $array)) {
848
                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
849
                                    ObjectException::DEPENDENCY_MISSING), $path);
850
                            }
851 3248
                        }
852
                    }
853 3248
                }
854 3224
            }
855
856 75
            $propertyFound = false;
857
            if (isset($properties[$key])) {
858
                /** @var Schema[] $properties */
859
                $prop = self::unboolSchema($properties[$key]);
860 3248
                $propertyFound = true;
861 2
                $found = true;
862 2
                if ($prop instanceof SchemaContract) {
863
                    $value = $prop->process(
864 2
                        $value,
865
                        isset($defaultApplied[$key]) ? $options->withDefault() : $options,
866
                        $path . '->properties:' . $key
867
                    );
868
                }
869 3248
            }
870 5
871 5
            /** @var Egg[] $nestedEggs */
872
            $nestedEggs = null;
873 5
            if (isset($nestedProperties[$key])) {
874 5
                $found = true;
875
                $nestedEggs = $nestedProperties[$key];
876
                // todo iterate all nested props?
877 3247
                $value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key);
878 29
            }
879 4
880
            if ($this->patternProperties !== null) {
881
                foreach ($this->patternProperties as $pattern => $propertySchema) {
882
                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
883 3247
                        $found = true;
884 1
                        $value = self::unboolSchema($propertySchema)->process($value, $options,
885
                            $path . '->patternProperties[' . strtr($pattern, array('~' => '~1', ':' => '~2')) . ']:' . $key);
886 3247
                        if (!$options->validateOnly && $import) {
887 3246
                            $result->addPatternPropertyName($pattern, $key);
0 ignored issues
show
Bug introduced by
The method addPatternPropertyName() does not exist on Swaggest\JsonSchema\Structure\ObjectItemContract. It seems like you code against a sub-type of said class. However, the method does not exist in Swaggest\JsonSchema\Stru...\ClassStructureContract. Are you sure you never get one of those? ( Ignorable by Annotation )

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

887
                            $result->/** @scrutinizer ignore-call */ 
888
                                     addPatternPropertyName($pattern, $key);
Loading history...
888 1181
                        }
889 1130
                        //break; // todo manage multiple import data properly (pattern accessor)
890
                    }
891
                }
892
            }
893
            if (!$found && $this->additionalProperties !== null) {
894
                if (!$options->skipValidation && $this->additionalProperties === false) {
895 3255
                    $this->fail(new ObjectException('Additional properties not allowed: ' . $key), $path);
896
                }
897
898
                if ($this->additionalProperties instanceof SchemaContract) {
899
                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
900
                }
901
902
                if ($import && !$this->useObjectAsArray && !$options->validateOnly) {
903
                    $result->addAdditionalPropertyName($key);
904
                }
905
            }
906
907
            $propertyName = $key;
908 1241
909
            if ($hasMapping) {
910 1241
                if ($this->properties !== null && isset($this->properties->__dataToProperty[self::DEFAULT_MAPPING][$key])) {
911 1241
                    // todo check performance of local map access
912 1041
                    $propertyName = $this->properties->__dataToProperty[self::DEFAULT_MAPPING][$key];
913 9
                }
914
            }
915
916 1035
            if ($options->mapping !== self::DEFAULT_MAPPING) {
917 6
                if (!$import) {
918
                    if ($this->properties !== null && isset($this->properties->__propertyToData[$options->mapping][$propertyName])) {
919
                        // todo check performance of local map access
920
                        $propertyName = $this->properties->__propertyToData[$options->mapping][$propertyName];
921 1229
                    }
922 1229
                }
923 1229
            }
924 876
925 876
            if (!$options->validateOnly && $nestedEggs && $import) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nestedEggs of type Swaggest\JsonSchema\Structure\Egg[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
926 705
                foreach ($nestedEggs as $nestedEgg) {
927 705
                    $result->setNestedProperty($key, $value, $nestedEgg);
928 705
                }
929
                if ($propertyFound) {
930 104
                    $result->$propertyName = $value;
931 104
                }
932 104
            } else {
933
                if (!$import && $hasMapping) {
934
                    if ($this->properties !== null && isset($this->properties->__propertyToData[$options->mapping][$propertyName])) {
935
                        $propertyName = $this->properties->__propertyToData[$options->mapping][$propertyName];
936
                    }
937
                }
938
939 1229
                if ($this->useObjectAsArray && $import) {
940 1229
                    $result[$propertyName] = $value;
941 1229
                } else {
942 1104
                    if ($found || !$import) {
943 94
                        $result->$propertyName = $value;
944 94
                    } elseif (!isset($result->$propertyName)) {
945
                        if (self::PROP_REF !== $propertyName || empty($result->__fromRef)) {
946 1104
                            $result->$propertyName = $value;
947 850
                        }
948 850
                    }
949 589
                }
950 6
            }
951
        }
952
953 1082
        return $result;
954
    }
955
956 1203
    /**
957 525
     * @param array $data
958 22
     * @param Context $options
959
     * @param string $path
960
     * @param array $result
961
     * @return mixed
962 1184
     * @throws InvalidValue
963
     * @throws \Exception
964 36
     * @throws \Swaggest\JsonDiff\Exception
965 36
     */
966 4
    private function processArray($data, Context $options, $path, $result)
967
    {
968 32
        $count = count($data);
969 7
        if (!$options->skipValidation) {
970
            if ($this->minItems !== null && $count < $this->minItems) {
971 25
                $this->fail(new ArrayException("Not enough items in array"), $path);
972 2
            }
973
974 25
            if ($this->maxItems !== null && $count > $this->maxItems) {
975 25
                $this->fail(new ArrayException("Too many items in array"), $path);
976
            }
977 25
        }
978 17
979 17
        $pathItems = 'items';
980 21
        $this->items = self::unboolSchema($this->items);
981
        if ($this->items instanceof SchemaContract) {
0 ignored issues
show
introduced by
$this->items is never a sub-type of Swaggest\JsonSchema\SchemaContract.
Loading history...
982
            $items = array();
983 25
            /**
984 8
             * @var null|bool|Schema $additionalItems
985
             */
986
            $additionalItems = $this->items;
987 1165
        } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
988
            $items = array();
989
            $additionalItems = true;
990
        } else { // listed items
991
            $items = $this->items;
992
            $additionalItems = $this->additionalItems;
993
            $pathItems = 'additionalItems';
994
        }
995
996
        /**
997 20
         * @var Schema|Schema[] $items
998
         */
999
        $itemsLen = is_array($items) ? count($items) : 0;
1000 20
        $index = 0;
1001 10
        foreach ($result as $key => $value) {
1002
            if ($index < $itemsLen) {
1003 10
                $itemSchema = self::unboolSchema($items[$index]);
1004
                $result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
1005 4
            } else {
1006 4
                if ($additionalItems instanceof SchemaContract) {
1007
                    $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
1008 10
                        . '[' . $index . ']:' . $index);
1009
                } elseif (!$options->skipValidation && $additionalItems === false) {
1010
                    $this->fail(new ArrayException('Unexpected array item'), $path);
1011
                }
1012
            }
1013
            ++$index;
1014
        }
1015
1016
        if (!$options->skipValidation && $this->uniqueItems) {
1017
            if (!UniqueItems::isValid($data)) {
1018
                $this->fail(new ArrayException('Array is not unique'), $path);
1019
            }
1020
        }
1021 3316
1022
        if (!$options->skipValidation && $this->contains !== null) {
1023 3316
            /** @var Schema|bool $contains */
1024 3316
            $contains = $this->contains;
1025
            if ($contains === false) {
0 ignored issues
show
introduced by
The condition $contains === false is always false.
Loading history...
1026 3316
                $this->fail(new ArrayException('Contains is false'), $path);
1027 2
            }
1028
            if ($count === 0) {
1029
                $this->fail(new ArrayException('Empty array fails contains constraint'), $path);
1030 3316
            }
1031 808
            if ($contains === true) {
0 ignored issues
show
introduced by
The condition $contains === true is always false.
Loading history...
1032
                $contains = self::unboolSchema($contains);
1033 808
            }
1034 794
            $containsOk = false;
1035 794
            foreach ($data as $key => $item) {
1036 6
                try {
1037 6
                    $contains->process($item, $options, $path . '->' . $key);
1038
                    $containsOk = true;
1039 6
                    break;
1040
                } catch (InvalidValue $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1041
                }
1042 794
            }
1043
            if (!$containsOk) {
1044
                $this->fail(new ArrayException('Array fails contains constraint'), $path);
1045 808
            }
1046 6
        }
1047
        return $result;
1048 808
    }
1049 6
1050 6
    /**
1051 6
     * @param mixed|string $data
1052 6
     * @param Context $options
1053 6
     * @param string $path
1054 6
     * @return bool|mixed|string
1055 6
     * @throws InvalidValue
1056
     */
1057
    private function processContent($data, Context $options, $path)
1058 6
    {
1059 6
        try {
1060 2
            if ($options->unpackContentMediaType) {
1061 2
                return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import);
1062 2
            } else {
1063 2
                Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true);
1064 2
            }
1065
        } catch (InvalidValue $exception) {
1066
            $this->fail($exception, $path);
1067
        }
1068 6
        return $data;
1069 6
    }
1070
1071
    /**
1072
     * @param mixed $data
1073 808
     * @param Context $options
1074
     * @param string $path
1075 1
     * @param mixed|null $result
1076 1
     * @return array|mixed|null|object|\stdClass
1077 1
     * @throws InvalidValue
1078
     * @throws \Exception
1079 808
     * @throws \Swaggest\JsonDiff\Exception
1080
     */
1081 808
    public function process($data, Context $options, $path = '#', $result = null)
1082
    {
1083
        $origData = $data;
0 ignored issues
show
Unused Code introduced by
The assignment to $origData is dead and can be removed.
Loading history...
1084 3316
        $import = $options->import;
1085
1086 3316
        if (!$import && $data instanceof SchemaExporter) {
1087 1
            $data = $data->exportSchema(); // Used to export ClassStructure::schema()
1088
        }
1089
1090 3316
        if (!$import && $data instanceof ObjectItemContract) {
1091
            $result = new \stdClass();
1092
1093
            if ('#' === $path) {
1094 3316
                $injectDefinitions = new ScopeExit(function () use ($result, $options) {
0 ignored issues
show
Unused Code introduced by
The assignment to $injectDefinitions is dead and can be removed.
Loading history...
1095 3316
                    foreach ($options->exportedDefinitions as $ref => $data) {
1096
                        if ($data !== null && ($ref[0] === '#' || $ref[1] === '/')) {
1097
                            JsonPointer::add($result, JsonPointer::splitPath($ref), $data,
1098 3316
                                /*JsonPointer::SKIP_IF_ISSET + */
1099 1450
                                JsonPointer::RECURSIVE_KEY_CREATION);
1100
                        }
1101
                    }
1102 3278
                });
1103 3257
            }
1104
1105
            if ($options->isRef) {
1106 3277
                $options->isRef = false;
1107 1513
            } else {
1108
                if ('#' !== $path && $refs = $data->getFromRefs()) {
1109
                    $ref = $refs[0];
1110 3277
                    if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1111 43
                        $exported = null;
1112
                        $options->exportedDefinitions[$ref] = &$exported;
1113
                        $options->isRef = true;
1114 3277
                        $exported = $this->process($data, $options, $ref);
1115 70
                        unset($exported);
1116
                    }
1117
1118 3277
                    $countRefs = count($refs);
1119 2331
                    for ($i = 1; $i < $countRefs; $i++) {
1120
                        $ref = $refs[$i];
1121
                        if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1122 3277
                            $exported = new \stdClass();
1123 1090
                            $exported->{self::PROP_REF} = $refs[$i - 1];
1124
                            $options->exportedDefinitions[$ref] = $exported;
1125
                        }
1126 3276
                    }
1127 26
1128
                    $result->{self::PROP_REF} = $refs[$countRefs - 1];
1129
                    return $result;
1130
                }
1131
            }
1132 3314
1133 107
            if ($options->circularReferences->contains($data)) {
1134
                /** @noinspection PhpIllegalArrayKeyTypeInspection */
1135
                $path = $options->circularReferences[$data];
1136 3314
                $result->{self::PROP_REF} = PointerUtil::getDataPointer($path, true);
1137 1763
                return $result;
1138
            }
1139
            $options->circularReferences->attach($data, $path);
1140 3314
1141 354
            $data = $data->jsonSerialize();
1142
        }
1143
1144 3314
        $path .= $this->getFromRefPath();
1145 3269
1146
        if (!$import && is_array($data) && $this->useObjectAsArray) {
1147
            $data = (object)$data;
1148 3311
        }
1149 1241
1150
        if (null !== $options->dataPreProcessor) {
1151
            $data = $options->dataPreProcessor->process($data, $this, $import);
1152 3311
        }
1153 20
1154 6
        if ($options->skipValidation) {
1155
            goto skipValidation;
1156 20
        }
1157
1158
        if ($this->type !== null) {
1159 3311
            $this->processType($data, $options, $path);
1160
        }
1161
1162
        if ($this->enum !== null) {
1163
            $this->processEnum($data, $path);
1164
        }
1165
1166 1
        if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) {
1167
            $this->processConst($data, $path);
1168 1
        }
1169 1
1170
        if ($this->not !== null) {
1171
            $this->processNot($data, $options, $path);
1172
        }
1173
1174
        if (is_string($data)) {
1175
            $this->processString($data, $path);
1176
        }
1177 1264
1178
        if (is_int($data) || is_float($data)) {
1179 1264
            $this->processNumeric($data, $path);
1180 1264
        }
1181
1182
        if ($this->if !== null) {
1183 16
            $result = $this->processIf($data, $options, $path);
1184
        }
1185 16
1186 16
        skipValidation:
1187 16
1188
        if ($result === null) {
1189
            $result = $data;
1190 4
        }
1191
1192 4
        if ($this->oneOf !== null) {
1193 4
            $result = $this->processOneOf($data, $options, $path);
1194 4
        }
1195
1196
        if ($this->anyOf !== null) {
1197 13
            $result = $this->processAnyOf($data, $options, $path);
1198
        }
1199 13
1200 13
        if ($this->allOf !== null) {
1201 13
            $result = $this->processAllOf($data, $options, $path);
1202
        }
1203
1204 4
        if (is_object($data)) {
1205
            $result = $this->processObject($data, $options, $path, $result);
1206 4
        }
1207 4
1208 4
        if (is_array($data)) {
1209
            $result = $this->processArray($data, $options, $path, $result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type Swaggest\JsonSchema\SchemaContract and Swaggest\JsonSchema\Structure\ClassStructure and Swaggest\JsonSchema\Structure\ObjectItemContract and object and stdClass; however, parameter $result of Swaggest\JsonSchema\Schema::processArray() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1209
            $result = $this->processArray($data, $options, $path, /** @scrutinizer ignore-type */ $result);
Loading history...
1210
        }
1211 8
1212
        if ($this->contentEncoding !== null || $this->contentMediaType !== null) {
1213 8
            if ($import && !is_string($data)) {
1214 8
                return $result;
1215 8
            }
1216
            $result = $this->processContent($data, $options, $path);
1217
        }
1218 1
1219
        return $result;
1220 1
    }
1221 1
1222 1
    /**
1223
     * @param boolean $useObjectAsArray
1224
     * @return Schema
1225
     */
1226
    public function setUseObjectAsArray($useObjectAsArray)
1227
    {
1228
        $this->useObjectAsArray = $useObjectAsArray;
1229
        return $this;
1230
    }
1231
1232
    /**
1233
     * @param InvalidValue $exception
1234
     * @param string $path
1235
     * @throws InvalidValue
1236
     */
1237 3
    private function fail(InvalidValue $exception, $path)
1238
    {
1239 3
        $exception->addPath($path);
1240 3
        throw $exception;
1241
    }
1242
1243
    public static function integer()
1244
    {
1245
        $schema = new static();
1246
        $schema->type = Type::INTEGER;
1247
        return $schema;
1248 5
    }
1249
1250 5
    public static function number()
1251 5
    {
1252
        $schema = new static();
1253 5
        $schema->type = Type::NUMBER;
1254 5
        return $schema;
1255
    }
1256
1257
    public static function string()
1258
    {
1259
        $schema = new static();
1260
        $schema->type = Type::STRING;
1261
        return $schema;
1262
    }
1263
1264
    public static function boolean()
1265
    {
1266
        $schema = new static();
1267
        $schema->type = Type::BOOLEAN;
1268
        return $schema;
1269
    }
1270
1271
    public static function object()
1272
    {
1273
        $schema = new static();
1274
        $schema->type = Type::OBJECT;
1275
        return $schema;
1276 1
    }
1277
1278 1
    public static function arr()
1279 1
    {
1280
        $schema = new static();
1281 1
        $schema->type = Type::ARR;
1282 1
        return $schema;
1283
    }
1284
1285 1
    public static function null()
1286
    {
1287 1
        $schema = new static();
1288 1
        $schema->type = Type::NULL;
1289
        return $schema;
1290
    }
1291
1292
1293
    /**
1294
     * @param Properties $properties
1295
     * @return SchemaContract
1296
     */
1297 5
    public function setProperties($properties)
1298
    {
1299 5
        $this->properties = $properties;
1300
        return $this;
1301
    }
1302 5
1303 5
    /**
1304
     * @param string $name
1305
     * @param Schema $schema
1306
     * @return $this
1307
     */
1308 5
    public function setProperty($name, $schema)
1309
    {
1310
        if (null === $this->properties) {
1311
            $this->properties = new Properties();
1312
        }
1313
        $this->properties->__set($name, $schema);
1314
        return $this;
1315
    }
1316
1317
    /**
1318 3327
     * @param string $name
1319
     * @param SchemaContract $schema
1320 3327
     * @return $this
1321 3327
     * @throws Exception
1322
     */
1323 3327
    public function setPatternProperty($name, $schema)
1324 1
    {
1325 1
        if (null === $this->patternProperties) {
1326 1
            $this->patternProperties = new Properties();
1327 1
        }
1328 1
        $this->patternProperties->__set($name, $schema);
1329
        return $this;
1330
    }
1331 3327
1332 108
1333 3299
    /** @var mixed[] */
1334 94
    private $metaItems = array();
1335
1336 3267
    public function addMeta($meta, $name = null)
1337
    {
1338
        if ($name === null) {
1339
            $name = get_class($meta);
1340
        }
1341
        $this->metaItems[$name] = $meta;
1342
        return $this;
1343
    }
1344 412
1345
    public function getMeta($name)
1346 412
    {
1347 412
        if (isset($this->metaItems[$name])) {
1348
            return $this->metaItems[$name];
1349 412
        }
1350 1
        return null;
1351 1
    }
1352 1
1353
    /**
1354
     * @param Context $options
1355 412
     * @return ObjectItemContract
1356 6
     */
1357 408
    public function makeObjectItem(Context $options = null)
1358 5
    {
1359
        if (null === $this->objectItemClass) {
1360 403
            return new ObjectItem();
1361
        } else {
1362
            $className = $this->objectItemClass;
1363
            if ($options !== null) {
1364 41
                if (isset($options->objectItemClassMapping[$className])) {
1365
                    $className = $options->objectItemClassMapping[$className];
1366 41
                }
1367
            }
1368
            return new $className;
1369 99
        }
1370
    }
1371 99
1372
    /**
1373
     * Resolves boolean schema into Schema instance.
1374
     *
1375
     * @param mixed $schema
1376
     * @return mixed|Schema
1377
     */
1378
    public static function unboolSchema($schema)
1379
    {
1380
        static $trueSchema;
1381
        static $falseSchema;
1382
1383
        if (null === $trueSchema) {
1384
            $trueSchema = new Schema();
1385
            $trueSchema->__booleanSchema = true;
1386
            $falseSchema = new Schema();
1387
            $falseSchema->not = $trueSchema;
1388
            $falseSchema->__booleanSchema = false;
1389
        }
1390 97
1391
        if ($schema === true) {
1392 97
            return $trueSchema;
1393
        } elseif ($schema === false) {
1394
            return $falseSchema;
1395
        } else {
1396
            return $schema;
1397
        }
1398
    }
1399
1400
    /**
1401
     * Converts bool value into an object schema.
1402
     *
1403
     * @param mixed $data
1404
     * @return \stdClass
1405
     */
1406
    public static function unboolSchemaData($data)
1407
    {
1408
        static $trueSchema;
1409
        static $falseSchema;
1410
1411
        if (null === $trueSchema) {
1412
            $trueSchema = new \stdClass();
1413
            $falseSchema = new \stdClass();
1414
            $falseSchema->not = $trueSchema;
1415
        }
1416
1417
        if ($data === true) {
1418
            return $trueSchema;
1419
        } elseif ($data === false) {
1420
            return $falseSchema;
1421
        } else {
1422
            return $data;
1423
        }
1424
    }
1425
1426
    public function getDefault()
1427
    {
1428
        return $this->default;
1429
    }
1430
1431
    /**
1432
     * @return bool
1433
     */
1434
    public function hasDefault()
1435
    {
1436
        return array_key_exists(self::DEFAULT_PROPERTY, $this->__arrayOfData);
1437
    }
1438
1439
    /**
1440
     * @nolint
1441
     */
1442
    public function getProperties()
1443
    {
1444
        return $this->properties;
1445
    }
1446
1447
    public function getObjectItemClass()
1448
    {
1449
        return $this->objectItemClass;
1450
    }
1451
1452
    /**
1453
     * @return string[]
1454
     */
1455
    public function getPropertyNames()
1456
    {
1457
        if (null === $this->properties) {
1458
            return array();
1459
        }
1460
        return array_keys($this->properties->toArray());
1461
    }
1462
1463
    /**
1464
     * @return string[]
1465
     */
1466
    public function getNestedPropertyNames()
1467
    {
1468
        if (null === $this->properties) {
1469
            return array();
1470
        }
1471
        return $this->properties->nestedPropertyNames;
1472
    }
1473
1474
}
1475