Completed
Push — master ( 4aca4f...a4adcd )
by Viacheslav
12:22 queued 02:30
created

Schema::hasDefault()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nop 0
crap 2
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
                            $ref->unsetImported();
740
                            $skipValidation = $options->skipValidation;
741
                            $options->skipValidation = true;
742
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString);
743 3268
                            if ($refResult instanceof ObjectItemContract) {
744 3247
                                if ($refResult->getFromRefs()) {
745 4
                                    $refResult = clone $refResult; // @todo check performance, consider option
746
                                }
747 3247
                                $refResult->setFromRef($refString);
748 3
                            }
749
                            $options->skipValidation = $skipValidation;
750 3247
751 19
                            if ($result instanceof WithResolvedValue) {
752 19
                                $result->setResolvedValue($refResult);
753 12
                            }
754
755
                            // Proceeding with unresolved data.
756
                            $data = $unresolvedData;
757
                        }
758 3268
                    } else {
759 3268
                        $this->process($data, $options, $path . '->$ref:' . $refString);
760 3268
                    }
761 3268
                }
762 3268
            } catch (InvalidValue $exception) {
763
                $this->fail($exception, $path);
764 43
            }
765
        }
766 41
767 6
        /** @var Schema[]|null $properties */
768 6
        $properties = null;
769 6
770
        $nestedProperties = null;
771
        if ($this->properties !== null) {
772
            $properties = $this->properties->toArray(); // todo call directly
773
            $nestedProperties = $this->properties->nestedProperties;
774
        }
775 3268
776 3258
777 1
        if (!$options->skipValidation) {
778
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
779
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
780 3257
            }
781
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
782 3257
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
783 73
            }
784 73
            if ($this->propertyNames !== null) {
785 63
                $propertyNames = self::unboolSchema($this->propertyNames);
786 63
                foreach ($array as $key => $tmp) {
787 63
                    $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

787
                    $propertyNames->/** @scrutinizer ignore-call */ 
788
                                    process($key, $options, $path . '->propertyNames:' . $key);
Loading history...
788 29
                }
789
            }
790 34
        }
791 31
792 18
        $defaultApplied = array();
793 18
        if ($import
794
            && !$options->validateOnly
795
            && $options->applyDefaults
796
            && $properties !== null
797
        ) {
798
            foreach ($properties as $key => $property) {
799
                $allowNull = false;
800 3257
                if ($property instanceof HasDefault) {
801 3257
                    if (!$property->hasDefault()) {
802
                        continue;
803 3247
                    }
804 3247
805 3247
                    $allowNull = true;
806 3247
                }
807 3190
808 3190
                // todo check when property is \stdClass `{}` here (RefTest)
809 3190
                if ($property instanceof SchemaContract) {
810 3190
                    if (!array_key_exists($key, $array)) {
811
                        $default = $property->getDefault();
812
                        if (null === $default && !$allowNull) { // @phpstan-ignore-line
813
                            continue;
814
                        }
815
816 3252
                        $defaultApplied[$key] = true;
817 3252
                        $array[$key] = $default;
818 6
                    }
819 6
                }
820
            }
821 6
        }
822
823
        /**
824 3252
         * @var string $key
825 178
         * @var mixed $value
826 178
         */
827 126
        foreach ($array as $key => $value) {
828 126
            if ($key === '' && PHP_VERSION_ID < 70100) {
829 126
                $this->fail(new InvalidValue('Empty property name'), $path);
830 99
            }
831 99
832
            $found = false;
833
834
            if (!$options->skipValidation && !empty($this->dependencies)) {
835
                $deps = $this->dependencies;
836
                if (isset($deps->$key)) {
837 3252
                    $dependencies = $deps->$key;
838 1023
                    $dependencies = self::unboolSchema($dependencies);
839 15
                    if ($dependencies instanceof SchemaContract) {
840
                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
841
                    } else {
842 1023
                        foreach ($dependencies as $item) {
843 1023
                            if (!array_key_exists($item, $array)) {
844
                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
845
                                    ObjectException::DEPENDENCY_MISSING), $path);
846 1016
                            }
847 1012
                        }
848
                    }
849
                }
850
            }
851 3248
852
            $propertyFound = false;
853 3248
            if (isset($properties[$key])) {
854 3224
                /** @var Schema[] $properties */
855
                $prop = self::unboolSchema($properties[$key]);
856 75
                $propertyFound = true;
857
                $found = true;
858
                if ($prop instanceof SchemaContract) {
859
                    $value = $prop->process(
860 3248
                        $value,
861 2
                        isset($defaultApplied[$key]) ? $options->withDefault() : $options,
862 2
                        $path . '->properties:' . $key
863
                    );
864 2
                }
865
            }
866
867
            /** @var Egg[] $nestedEggs */
868
            $nestedEggs = null;
869 3248
            if (isset($nestedProperties[$key])) {
870 5
                $found = true;
871 5
                $nestedEggs = $nestedProperties[$key];
872
                // todo iterate all nested props?
873 5
                $value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key);
874 5
            }
875
876
            if ($this->patternProperties !== null) {
877 3247
                foreach ($this->patternProperties as $pattern => $propertySchema) {
878 29
                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
879 4
                        $found = true;
880
                        $value = self::unboolSchema($propertySchema)->process($value, $options,
881
                            $path . '->patternProperties[' . strtr($pattern, array('~' => '~1', ':' => '~2')) . ']:' . $key);
882
                        if (!$options->validateOnly && $import) {
883 3247
                            $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

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

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