Completed
Push — master ( 0f5097...9c8f19 )
by Viacheslav
12:56 queued 02:59
created

src/Schema.php (1 issue)

Labels
Severity
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
35
{
36
    const ENUM_NAMES_PROPERTY = 'x-enum-names';
37
    const CONST_PROPERTY = 'const';
38
39
    const DEFAULT_MAPPING = 'default';
40
41
    const VERSION_AUTO = 'a';
42
    const VERSION_DRAFT_04 = 4;
43
    const VERSION_DRAFT_06 = 6;
44
    const VERSION_DRAFT_07 = 7;
45
46
    const PROP_REF = '$ref';
47
    const PROP_ID = '$id';
48
    const PROP_ID_D4 = 'id';
49
50
    // Object
51
    /** @var null|Properties */
52
    public $properties;
53
    /** @var SchemaContract|bool */
54
    public $additionalProperties;
55
    /** @var SchemaContract[]|Properties */
56
    public $patternProperties;
57
    /** @var string[][]|Schema[]|\stdClass */
58
    public $dependencies;
59
60
    // Array
61
    /** @var null|SchemaContract|SchemaContract[] */
62
    public $items;
63
    /** @var null|SchemaContract|bool */
64
    public $additionalItems;
65
66
    /** @var SchemaContract[] */
67
    public $allOf;
68
    /** @var SchemaContract */
69
    public $not;
70
    /** @var SchemaContract[] */
71
    public $anyOf;
72
    /** @var SchemaContract[] */
73
    public $oneOf;
74
75
    /** @var SchemaContract */
76
    public $if;
77
    /** @var SchemaContract */
78
    public $then;
79
    /** @var SchemaContract */
80
    public $else;
81
82
83
    public $objectItemClass;
84
85
    /**
86
     * @todo check usages/deprecate
87
     * @var bool
88
     */
89
    private $useObjectAsArray = false;
90
91
    private $__booleanSchema;
92 4
93
    public function addPropertyMapping($dataName, $propertyName, $mapping = self::DEFAULT_MAPPING)
94 4
    {
95
        if (null === $this->properties) {
96
            $this->properties = new Properties();
97
        }
98 4
99 4
        $this->properties->addPropertyMapping($dataName, $propertyName, $mapping);
100
        return $this;
101
    }
102
103
    /**
104
     * @param mixed $data
105
     * @param Context|null $options
106
     * @return SchemaContract
107
     * @throws Exception
108
     * @throws InvalidValue
109
     * @throws \Exception
110 3298
     */
111
    public static function import($data, Context $options = null)
112 3298
    {
113 33
        if (null === $options) {
114
            $options = new Context();
115
        }
116 3298
117
        $options->applyDefaults = false;
118 3298
119
        if (isset($options->schemasCache) && is_object($data)) {
120
            if ($options->schemasCache->contains($data)) {
121
                return $options->schemasCache->offsetGet($data);
122
            } else {
123
                $schema = parent::import($data, $options);
124
                $options->schemasCache->attach($data, $schema);
125
                return $schema;
126
            }
127
        }
128
129 3298
        // string $data is expected to be $ref uri
130 6
        if (is_string($data)) {
131
            $data = (object)array(self::PROP_REF => $data);
132
        }
133 3298
134 3298
        $data = self::unboolSchema($data);
135 72
        if ($data instanceof SchemaContract) {
136
            return $data;
137
        }
138 3226
139
        return parent::import($data, $options);
140
    }
141
142
    /**
143
     * @param mixed $data
144
     * @param Context|null $options
145
     * @return array|mixed|null|object|\stdClass
146
     * @throws Exception
147
     * @throws InvalidValue
148
     * @throws \Exception
149 3323
     */
150
    public function in($data, Context $options = null)
151 3323
    {
152 72
        if (null !== $this->__booleanSchema) {
153 36
            if ($this->__booleanSchema) {
154 36
                return $data;
155 18
            } elseif (empty($options->skipValidation)) {
156
                $this->fail(new InvalidValue('Denied by false schema'), '#');
157
            }
158
        }
159 3269
160 69
        if ($options === null) {
161
            $options = new Context();
162
        }
163 3269
164
        $options->import = true;
165 3269
166 2936
        if ($options->refResolver === null) {
167
            $options->refResolver = new RefResolver($data);
168 1766
        } else {
169
            $options->refResolver->setRootData($data);
170
        }
171 3269
172 3191
        if ($options->remoteRefProvider) {
173
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
174
        }
175 3269
176
        $options->refResolver->preProcessReferences($data, $options);
177 3269
178
        return $this->process($data, $options, '#');
179
    }
180
181
182
    /**
183
     * @param mixed $data
184
     * @param Context|null $options
185
     * @return array|mixed|null|object|\stdClass
186
     * @throws InvalidValue
187
     * @throws \Exception
188 2452
     */
189
    public function out($data, Context $options = null)
190 2452
    {
191 1007
        if ($options === null) {
192
            $options = new Context();
193
        }
194 2452
195 2452
        $options->circularReferences = new \SplObjectStorage();
196 2452
        $options->import = false;
197
        return $this->process($data, $options);
198
    }
199
200
    /**
201
     * @param mixed $data
202
     * @param Context $options
203
     * @param string $path
204
     * @throws InvalidValue
205
     * @throws \Exception
206 3257
     */
207
    private function processType(&$data, Context $options, $path = '#')
208 3257
    {
209
        if ($options->tolerateStrings && is_string($data)) {
210
            $valid = Type::readString($this->type, $data);
211 3257
        } else {
212
            $valid = Type::isValid($this->type, $data, $options->version);
213 3257
        }
214 696
        if (!$valid) {
215 696
            $this->fail(new TypeException(ucfirst(
216 696
                    implode(', ', is_array($this->type) ? $this->type : array($this->type))
217 696
                    . ' expected, ' . json_encode($data) . ' received')
218
            ), $path);
219 3255
        }
220
    }
221
222
    /**
223
     * @param mixed $data
224
     * @param string $path
225
     * @throws InvalidValue
226
     * @throws \Exception
227 1513
     */
228
    private function processEnum($data, $path = '#')
229 1513
    {
230 1513
        $enumOk = false;
231 1513
        foreach ($this->enum as $item) {
232 1493
            if ($item === $data) {
233 1493
                $enumOk = true;
234
                break;
235 1449
            } else {
236 12
                if (is_array($item) || is_object($item)) {
237 12
                    $diff = new JsonDiff($item, $data, JsonDiff::STOP_ON_DIFF);
238 3
                    if ($diff->getDiffCnt() === 0) {
239 3
                        $enumOk = true;
240
                        break;
241
                    }
242
                }
243
            }
244 1513
        }
245 107
        if (!$enumOk) {
246
            $this->fail(new EnumException('Enum failed, enum: ' . json_encode($this->enum) . ', data: ' . json_encode($data)), $path);
247 1496
        }
248
    }
249
250
    /**
251
     * @param mixed $data
252
     * @param string $path
253
     * @throws InvalidValue
254
     * @throws \Swaggest\JsonDiff\Exception
255 43
     */
256
    private function processConst($data, $path)
257 43
    {
258 37
        if ($this->const !== $data) {
259 37
            if ((is_object($this->const) && is_object($data))
260 15
                || (is_array($this->const) && is_array($data))) {
261 15
                $diff = new JsonDiff($this->const, $data,
262 15
                    JsonDiff::STOP_ON_DIFF);
263 15
                if ($diff->getDiffCnt() != 0) {
264
                    $this->fail(new ConstException('Const failed'), $path);
265
                }
266 22
            } else {
267
                $this->fail(new ConstException('Const failed'), $path);
268
            }
269 20
        }
270
    }
271
272
    /**
273
     * @param mixed $data
274
     * @param Context $options
275
     * @param string $path
276
     * @throws InvalidValue
277
     * @throws \Exception
278
     * @throws \Swaggest\JsonDiff\Exception
279 70
     */
280
    private function processNot($data, Context $options, $path)
281 70
    {
282
        $exception = false;
283 70
        try {
284 16
            self::unboolSchema($this->not)->process($data, $options, $path . '->not');
285
        } catch (InvalidValue $exception) {
286
            // Expected exception
287 70
        }
288 56
        if ($exception === false) {
289
            $this->fail(new LogicException('Not ' . json_encode($this->not) . ' expected, ' . json_encode($data) . ' received'), $path . '->not');
290 16
        }
291
    }
292
293
    /**
294
     * @param string $data
295
     * @param string $path
296
     * @throws InvalidValue
297 2331
     */
298
    private function processString($data, $path)
299 2331
    {
300 38
        if ($this->minLength !== null) {
301 9
            if (mb_strlen($data, 'UTF-8') < $this->minLength) {
302
                $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
303
            }
304 2325
        }
305 43
        if ($this->maxLength !== null) {
306 19
            if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
307
                $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
308
            }
309 2322
        }
310 18
        if ($this->pattern !== null) {
311 4
            if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
312 4
                $this->fail(new StringException(json_encode($data) . ' does not match to '
313
                    . $this->pattern, StringException::PATTERN_MISMATCH), $path);
314
            }
315 2322
        }
316 445
        if ($this->format !== null) {
317 445
            $validationError = Format::validationError($this->format, $data);
318 150
            if ($validationError !== null) {
319 131
                if (!($this->format === "uri" && substr($path, -3) === ':id')) {
320
                    $this->fail(new StringException($validationError), $path);
321
                }
322
            }
323 2322
        }
324
    }
325
326
    /**
327
     * @param float|int $data
328
     * @param string $path
329
     * @throws InvalidValue
330 1090
     */
331
    private function processNumeric($data, $path)
332 1090
    {
333 39
        if ($this->multipleOf !== null) {
334 39
            $div = $data / $this->multipleOf;
335 15
            if ($div != (int)$div) {
336
                $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
337
            }
338
        }
339 1090
340 32
        if ($this->exclusiveMaximum !== null && !is_bool($this->exclusiveMaximum)) {
341 18
            if ($data >= $this->exclusiveMaximum) {
342 18
                $this->fail(new NumericException(
343 18
                    'Value less or equal than ' . $this->exclusiveMaximum . ' expected, ' . $data . ' received',
344
                    NumericException::MAXIMUM), $path);
345
            }
346
        }
347 1090
348 24
        if ($this->exclusiveMinimum !== null && !is_bool($this->exclusiveMinimum)) {
349 12
            if ($data <= $this->exclusiveMinimum) {
350 12
                $this->fail(new NumericException(
351 12
                    'Value more or equal than ' . $this->exclusiveMinimum . ' expected, ' . $data . ' received',
352
                    NumericException::MINIMUM), $path);
353
            }
354
        }
355 1090
356 46
        if ($this->maximum !== null) {
357 3
            if ($this->exclusiveMaximum === true) {
358 2
                if ($data >= $this->maximum) {
359 2
                    $this->fail(new NumericException(
360 3
                        'Value less or equal than ' . $this->maximum . ' expected, ' . $data . ' received',
361
                        NumericException::MAXIMUM), $path);
362
                }
363 43
            } else {
364 14
                if ($data > $this->maximum) {
365 14
                    $this->fail(new NumericException(
366 14
                        'Value less than ' . $this->maximum . ' expected, ' . $data . ' received',
367
                        NumericException::MAXIMUM), $path);
368
                }
369
            }
370
        }
371 1090
372 524
        if ($this->minimum !== null) {
373 93
            if ($this->exclusiveMinimum === true) {
374 2
                if ($data <= $this->minimum) {
375 2
                    $this->fail(new NumericException(
376 93
                        'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
377
                        NumericException::MINIMUM), $path);
378
                }
379 449
            } else {
380 46
                if ($data < $this->minimum) {
381 46
                    $this->fail(new NumericException(
382 46
                        'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
383
                        NumericException::MINIMUM), $path);
384
                }
385
            }
386 1089
        }
387
    }
388
389
    /**
390
     * @param mixed $data
391
     * @param Context $options
392
     * @param string $path
393
     * @return array|mixed|null|object|\stdClass
394
     * @throws InvalidValue
395
     * @throws \Exception
396
     * @throws \Swaggest\JsonDiff\Exception
397 107
     */
398
    private function processOneOf($data, Context $options, $path)
399 107
    {
400 107
        $successes = 0;
401 107
        $failures = '';
402 107
        $subErrors = [];
403 107
        $skipValidation = false;
404 41
        if ($options->skipValidation) {
405 41
            $skipValidation = true;
406
            $options->skipValidation = false;
407
        }
408 107
409 107
        $result = $data;
410
        foreach ($this->oneOf as $index => $item) {
411 107
            try {
412 85
                $result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf[' . $index . ']');
413 85
                $successes++;
414 85
                if ($successes > 1 || $options->skipValidation) {
415
                    break;
416 75
                }
417 75
            } catch (InvalidValue $exception) {
418 75
                $subErrors[$index] = $exception;
419
                $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
420
                // Expected exception
421
            }
422 107
        }
423 41
        if ($skipValidation) {
424 41
            $options->skipValidation = true;
425 8
            if ($successes === 0) {
426
                $result = self::unboolSchema($this->oneOf[0])->process($data, $options, $path . '->oneOf[0]');
427
            }
428
        }
429 107
430 66
        if (!$options->skipValidation) {
431 20
            if ($successes === 0) {
432 20
                $exception = new LogicException('No valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}");
433 20
                $exception->error = 'No valid results for oneOf';
434 20
                $exception->subErrors = $subErrors;
435 52
                $this->fail($exception, $path);
436 17
            } elseif ($successes > 1) {
437 17
                $exception = new LogicException('More than 1 valid result for oneOf: '
438 17
                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf {'
439 17
                    . "\n" . substr($failures, 0, -1) . "\n}");
440 17
                $exception->error = 'More than 1 valid result for oneOf';
441 17
                $exception->subErrors = $subErrors;
442
                $this->fail($exception, $path);
443
            }
444 77
        }
445
        return $result;
446
    }
447
448
    /**
449
     * @param mixed $data
450
     * @param Context $options
451
     * @param string $path
452
     * @return array|mixed|null|object|\stdClass
453
     * @throws InvalidValue
454
     * @throws \Exception
455
     * @throws \Swaggest\JsonDiff\Exception
456 1763
     */
457
    private function processAnyOf($data, Context $options, $path)
458 1763
    {
459 1763
        $successes = 0;
460 1763
        $failures = '';
461 1763
        $subErrors = [];
462 1763
        $result = $data;
463
        foreach ($this->anyOf as $index => $item) {
464 1763
            try {
465 1757
                $result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf[' . $index . ']');
466 1757
                $successes++;
467 1757
                if ($successes) {
468
                    break;
469 445
                }
470 445
            } catch (InvalidValue $exception) {
471 445
                $subErrors[$index] = $exception;
472
                $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
473
                // Expected exception
474
            }
475 1763
        }
476 31
        if (!$successes && !$options->skipValidation) {
477 31
            $exception = new LogicException('No valid results for anyOf {' . "\n"
478 31
                . substr(Helper::padLines(' ', $failures, false), 0, -1)
479 31
                . "\n}");
480 31
            $exception->error = 'No valid results for anyOf';
481 31
            $exception->subErrors = $subErrors;
482
            $this->fail($exception, $path);
483 1757
        }
484
        return $result;
485
    }
486
487
    /**
488
     * @param mixed $data
489
     * @param Context $options
490
     * @param string $path
491
     * @return array|mixed|null|object|\stdClass
492
     * @throws InvalidValue
493
     * @throws \Exception
494
     * @throws \Swaggest\JsonDiff\Exception
495 354
     */
496
    private function processAllOf($data, Context $options, $path)
497 354
    {
498 354
        $result = $data;
499 354
        foreach ($this->allOf as $index => $item) {
500
            $result = self::unboolSchema($item)->process($data, $options, $path . '->allOf[' . $index . ']');
501 314
        }
502
        return $result;
503
    }
504
505
    /**
506
     * @param mixed $data
507
     * @param Context $options
508
     * @param string $path
509
     * @return array|mixed|null|object|\stdClass
510
     * @throws InvalidValue
511
     * @throws \Exception
512
     * @throws \Swaggest\JsonDiff\Exception
513 26
     */
514
    private function processIf($data, Context $options, $path)
515 26
    {
516
        $valid = true;
517 26
        try {
518 13
            self::unboolSchema($this->if)->process($data, $options, $path . '->if');
519 13
        } catch (InvalidValue $exception) {
520
            $valid = false;
521 26
        }
522 18
        if ($valid) {
523 18
            if ($this->then !== null) {
524
                return self::unboolSchema($this->then)->process($data, $options, $path . '->then');
525
            }
526 13
        } else {
527 6
            if ($this->else !== null) {
528
                return self::unboolSchema($this->else)->process($data, $options, $path . '->else');
529
            }
530 10
        }
531
        return null;
532
    }
533
534
    /**
535
     * @param array $array
536
     * @param Context $options
537
     * @param string $path
538
     * @throws InvalidValue
539 168
     */
540
    private function processObjectRequired($array, Context $options, $path)
541 168
    {
542 164
        foreach ($this->required as $item) {
543 74
            if (!array_key_exists($item, $array)) {
544
                $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($array, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
545
            }
546 136
        }
547
    }
548
549
    /**
550
     * @param object $data
551
     * @param Context $options
552
     * @param string $path
553
     * @param ObjectItemContract|null $result
554
     * @return array|null|ClassStructure|ObjectItemContract|SchemaContract
555
     * @throws InvalidValue
556
     * @throws \Exception
557
     * @throws \Swaggest\JsonDiff\Exception
558 3269
     */
559
    private function processObject($data, Context $options, $path, $result = null)
560 3269
    {
561
        $import = $options->import;
562 3269
563
        $hasMapping = $this->properties !== null && isset($this->properties->__dataToProperty[$options->mapping]);
564 3269
565
        $array = !$data instanceof \stdClass ? get_object_vars($data) : (array)$data;
566
567 3269
        // convert imported data to default mapping before validation
568 2
        if ($import && $options->mapping !== self::DEFAULT_MAPPING) {
569 2
            if ($this->properties !== null && isset($this->properties->__dataToProperty[$options->mapping])) {
570 2
                foreach ($this->properties->__dataToProperty[$options->mapping] as $dataName => $propertyName) {
571
                    if (!isset($array[$dataName])) {
572
                        continue;
573
                    }
574 2
575 2
                    $propertyName = isset($this->properties->__propertyToData[self::DEFAULT_MAPPING][$propertyName])
576 2
                        ? $this->properties->__propertyToData[self::DEFAULT_MAPPING][$propertyName]
577 2
                        : $propertyName;
578 2
                    if ($propertyName !== $dataName) {
579 2
                        $array[$propertyName] = $array[$dataName];
580
                        unset($array[$dataName]);
581
                    }
582
                }
583
            }
584
        }
585 3269
586 168
        if (!$options->skipValidation && $this->required !== null) {
587
            $this->processObjectRequired($array, $options, $path);
588
        }
589
590 3268
        // build result entity
591 3251
        if ($import) {
592
            if (!$options->validateOnly) {
593 3251
594 1
                if ($this->useObjectAsArray) {
595 3250
                    $result = array();
596
                } else {
597 3250
                    //* todo check performance impact
598 1186
                    if (null === $this->objectItemClass) {
599
                        if (!$result instanceof ObjectItemContract) {
600 3239
                            $result = new ObjectItem();
601 3239
                            $result->setDocumentPath($path);
602
                        }
603
                    } else {
604
                        $className = $this->objectItemClass;
605
                        if ($options->objectItemClassMapping !== null) {
606 3239
                            if (isset($options->objectItemClassMapping[$className])) {
607
                                $className = $options->objectItemClassMapping[$className];
608
                            }
609
                        }
610
                        if (null === $result || get_class($result) !== $className) {
611 3250
                            $result = new $className;
612 3235
                            //* todo check performance impact
613 3235
                            if ($result instanceof ClassStructure) {
614
                                $result->setDocumentPath($path);
615
                                if ($result->__validateOnSet) {
616
                                    $result->__validateOnSet = false;
617
                                    /** @noinspection PhpUnusedLocalVariableInspection */
618
                                    /* todo check performance impact
619
                                    $validateOnSetHandler = new ScopeExit(function () use ($result) {
620
                                        $result->__validateOnSet = true;
621
                                    });
622
                                    //*/
623
                                }
624 3250
                            }
625 3250
                            //*/
626
                        }
627
                    }
628
                    //*/
629
                }
630
            }
631
        }
632
633
        // @todo better check for schema id
634 3268
635 3268
        if ($import
636 3268
            && isset($array[Schema::PROP_ID_D4])
637 3268
            && ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO)
638 42
            && is_string($array[Schema::PROP_ID_D4])) {
639 42
            $id = $array[Schema::PROP_ID_D4];
640 42
            $refResolver = $options->refResolver;
641
            $parentScope = $refResolver->updateResolutionScope($id);
642
            /** @noinspection PhpUnusedLocalVariableInspection */
643 42
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
644 42
                $refResolver->setResolutionScope($parentScope);
645
            });
646
        }
647 3268
648 3268
        if ($import
649 3268
            && isset($array[self::PROP_ID])
650 3268
            && ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO)
651 116
            && is_string($array[self::PROP_ID])) {
652 116
            $id = $array[self::PROP_ID];
653 116
            $refResolver = $options->refResolver;
654
            $parentScope = $refResolver->updateResolutionScope($id);
655
            /** @noinspection PhpUnusedLocalVariableInspection */
656 116
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
657 116
                $refResolver->setResolutionScope($parentScope);
658
            });
659
        }
660
661 3268
        // check $ref
662
        if ($import) {
663
            try {
664 3251
665 3251
                $refProperty = null;
666
                $dereference = $options->dereference;
667 3251
668 450
                if ($this->properties !== null && isset($array[self::PROP_REF])) {
669
                    $refPropName = self::PROP_REF;
670 450
                    if ($hasMapping) {
671 14
                        if (isset($this->properties->__dataToProperty[$options->mapping][self::PROP_REF])) {
672
                            $refPropName = $this->properties->__dataToProperty[$options->mapping][self::PROP_REF];
673
                        }
674
                    }
675
676 3251
                    $refProperty = $this->properties[$refPropName];
677 3251
678 3251
                    if (isset($refProperty)) {
679
                        $dereference = $refProperty->format === Format::URI_REFERENCE;
680 438
                    }
681
                }
682
683 438
                if (
684 428
                    isset($array[self::PROP_REF])
685 438
                    && is_string($array[self::PROP_REF])
686 26
                    && $dereference
687
                ) {
688
                    $refString = $array[self::PROP_REF];
689
690 412
                    // todo check performance impact
691 412
                    if ($refString === 'http://json-schema.org/draft-04/schema#'
692
                        || $refString === 'http://json-schema.org/draft-06/schema#'
693
                        || $refString === 'http://json-schema.org/draft-07/schema#') {
694 412
                        return Schema::schema();
695 412
                    }
696
697 412
                    // TODO consider process # by reference here ?
698 412
                    $refResolver = $options->refResolver;
699 412
                    $preRefScope = $refResolver->getResolutionScope();
700 412
                    /** @noinspection PhpUnusedLocalVariableInspection */
701 187
                    $deferRefScope = new ScopeExit(function () use ($preRefScope, $refResolver) {
702 187
                        $refResolver->setResolutionScope($preRefScope);
703
                    });
704 412
705
                    $ref = $refResolver->resolveReference($refString);
706 412
                    $unresolvedData = $data;
707 412
                    $data = self::unboolSchemaData($ref->getData());
708 412
                    if (!$options->validateOnly) {
709 47
                        if ($ref->isImported()) {
710
                            $refResult = $ref->getImported();
711 412
                            return $refResult;
712
                        }
713 412
                        $ref->setImported($result);
714 1
                        try {
715 1
                            // Best effort dereference delivery.
716 1
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString, $result);
717
                            if ($refResult instanceof ObjectItemContract) {
718 412
                                if ($refResult->getFromRefs()) {
719
                                    $refResult = clone $refResult; // @todo check performance, consider option
720 3251
                                }
721
                                $refResult->setFromRef($refString);
722
                            }
723 1
                            $ref->setImported($refResult);
724 1
                            return $refResult;
725
                        } catch (InvalidValue $exception) {
726
                            $ref->unsetImported();
727
                            $skipValidation = $options->skipValidation;
728
                            $options->skipValidation = true;
729 3268
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString);
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
                            $options->skipValidation = $skipValidation;
737
738 590
                            if ($result instanceof WithResolvedValue) {
739
                                $result->setResolvedValue($refResult);
740
                            }
741
742
                            // Proceeding with unresolved data.
743 3268
                            $data = $unresolvedData;
744 3247
                        }
745 4
                    } else {
746
                        $this->process($data, $options, $path . '->$ref:' . $refString);
747 3247
                    }
748 3
                }
749
            } catch (InvalidValue $exception) {
750 3247
                $this->fail($exception, $path);
751 19
            }
752 19
        }
753 12
754
        /** @var Schema[]|null $properties */
755
        $properties = null;
756
757
        $nestedProperties = null;
758 3268
        if ($this->properties !== null) {
759 3268
            $properties = $this->properties->toArray(); // todo call directly
760 3268
            $nestedProperties = $this->properties->nestedProperties;
761 3268
        }
762 3268
763
764 43
        if (!$options->skipValidation) {
765
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
766 41
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
767 6
            }
768 6
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
769 6
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
770
            }
771
            if ($this->propertyNames !== null) {
772
                $propertyNames = self::unboolSchema($this->propertyNames);
773
                foreach ($array as $key => $tmp) {
774
                    $propertyNames->process($key, $options, $path . '->propertyNames:' . $key);
775 3268
                }
776 3258
            }
777 1
        }
778
779
        $defaultApplied = array();
780 3257
        if ($import
781
            && !$options->validateOnly
782 3257
            && $options->applyDefaults
783 73
            && $properties !== null
784 73
        ) {
785 63
            foreach ($properties as $key => $property) {
786 63
                // todo check when property is \stdClass `{}` here (RefTest)
787 63
                if ($property instanceof SchemaContract && null !== $default = $property->getDefault()) {
788 29
                    if (!array_key_exists($key, $array)) {
789
                        $defaultApplied[$key] = true;
790 34
                        $array[$key] = $default;
791 31
                    }
792 18
                }
793 18
            }
794
        }
795
796
        /**
797
         * @var string $key
798
         * @var mixed $value
799
         */
800 3257
        foreach ($array as $key => $value) {
801 3257
            if ($key === '' && PHP_VERSION_ID < 71000) {
802
                $this->fail(new InvalidValue('Empty property name'), $path);
803 3247
            }
804 3247
805 3247
            $found = false;
806 3247
807 3190
            if (!$options->skipValidation && !empty($this->dependencies)) {
808 3190
                $deps = $this->dependencies;
809 3190
                if (isset($deps->$key)) {
810 3190
                    $dependencies = $deps->$key;
811
                    $dependencies = self::unboolSchema($dependencies);
812
                    if ($dependencies instanceof SchemaContract) {
813
                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
814
                    } else {
815
                        foreach ($dependencies as $item) {
816 3252
                            if (!array_key_exists($item, $array)) {
817 3252
                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
818 6
                                    ObjectException::DEPENDENCY_MISSING), $path);
819 6
                            }
820
                        }
821 6
                    }
822
                }
823
            }
824 3252
825 178
            $propertyFound = false;
826 178
            if (isset($properties[$key])) {
827 126
                /** @var Schema[] $properties */
828 126
                $prop = self::unboolSchema($properties[$key]);
829 126
                $propertyFound = true;
830 99
                $found = true;
831 99
                if ($prop instanceof SchemaContract) {
832
                    $value = $prop->process(
833
                        $value,
834
                        isset($defaultApplied[$key]) ? $options->withDefault() : $options,
835
                        $path . '->properties:' . $key
836
                    );
837 3252
                }
838 1023
            }
839 15
840
            /** @var Egg[] $nestedEggs */
841
            $nestedEggs = null;
842 1023
            if (isset($nestedProperties[$key])) {
843 1023
                $found = true;
844
                $nestedEggs = $nestedProperties[$key];
845
                // todo iterate all nested props?
846 1016
                $value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key);
847 1012
            }
848
849
            if ($this->patternProperties !== null) {
850
                foreach ($this->patternProperties as $pattern => $propertySchema) {
851 3248
                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
852
                        $found = true;
853 3248
                        $value = self::unboolSchema($propertySchema)->process($value, $options,
854 3224
                            $path . '->patternProperties[' . strtr($pattern, array('~' => '~1', ':' => '~2')) . ']:' . $key);
855
                        if (!$options->validateOnly && $import) {
856 75
                            $result->addPatternPropertyName($pattern, $key);
857
                        }
858
                        //break; // todo manage multiple import data properly (pattern accessor)
859
                    }
860 3248
                }
861 2
            }
862 2
            if (!$found && $this->additionalProperties !== null) {
863
                if (!$options->skipValidation && $this->additionalProperties === false) {
864 2
                    $this->fail(new ObjectException('Additional properties not allowed: ' . $key), $path);
865
                }
866
867
                if ($this->additionalProperties instanceof SchemaContract) {
868
                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
869 3248
                }
870 5
871 5
                if ($import && !$this->useObjectAsArray && !$options->validateOnly) {
872
                    $result->addAdditionalPropertyName($key);
873 5
                }
874 5
            }
875
876
            $propertyName = $key;
877 3247
878 29
            if ($hasMapping) {
879 4
                if ($this->properties !== null && isset($this->properties->__dataToProperty[self::DEFAULT_MAPPING][$key])) {
880
                    // todo check performance of local map access
881
                    $propertyName = $this->properties->__dataToProperty[self::DEFAULT_MAPPING][$key];
882
                }
883 3247
            }
884 1
885
            if ($options->mapping !== self::DEFAULT_MAPPING) {
886 3247
                if (!$import) {
887 3246
                    if ($this->properties !== null && isset($this->properties->__propertyToData[$options->mapping][$propertyName])) {
888 1181
                        // todo check performance of local map access
889 1130
                        $propertyName = $this->properties->__propertyToData[$options->mapping][$propertyName];
890
                    }
891
                }
892
            }
893
894
            if (!$options->validateOnly && $nestedEggs && $import) {
895 3255
                foreach ($nestedEggs as $nestedEgg) {
896
                    $result->setNestedProperty($key, $value, $nestedEgg);
897
                }
898
                if ($propertyFound) {
899
                    $result->$propertyName = $value;
900
                }
901
            } else {
902
                if (!$import && $hasMapping) {
903
                    if ($this->properties !== null && isset($this->properties->__propertyToData[$options->mapping][$propertyName])) {
904
                        $propertyName = $this->properties->__propertyToData[$options->mapping][$propertyName];
905
                    }
906
                }
907
908 1241
                if ($this->useObjectAsArray && $import) {
909
                    $result[$propertyName] = $value;
910 1241
                } else {
911 1241
                    if ($found || !$import) {
912 1041
                        $result->$propertyName = $value;
913 9
                    } elseif (!isset($result->$propertyName)) {
914
                        $result->$propertyName = $value;
915
                    }
916 1035
                }
917 6
            }
918
        }
919
920
        return $result;
921 1229
    }
922 1229
923 1229
    /**
924 876
     * @param array $data
925 876
     * @param Context $options
926 705
     * @param string $path
927 705
     * @param array $result
928 705
     * @return mixed
929
     * @throws InvalidValue
930 104
     * @throws \Exception
931 104
     * @throws \Swaggest\JsonDiff\Exception
932 104
     */
933
    private function processArray($data, Context $options, $path, $result)
934
    {
935
        $count = count($data);
936
        if (!$options->skipValidation) {
937
            if ($this->minItems !== null && $count < $this->minItems) {
938
                $this->fail(new ArrayException("Not enough items in array"), $path);
939 1229
            }
940 1229
941 1229
            if ($this->maxItems !== null && $count > $this->maxItems) {
942 1104
                $this->fail(new ArrayException("Too many items in array"), $path);
943 94
            }
944 94
        }
945
946 1104
        $pathItems = 'items';
947 850
        $this->items = self::unboolSchema($this->items);
948 850
        if ($this->items instanceof SchemaContract) {
949 589
            $items = array();
950 6
            $additionalItems = $this->items;
951
        } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
952
            $items = array();
953 1082
            $additionalItems = true;
954
        } else { // listed items
955
            $items = $this->items;
956 1203
            $additionalItems = $this->additionalItems;
957 525
            $pathItems = 'additionalItems';
958 22
        }
959
960
        /**
961
         * @var Schema|Schema[] $items
962 1184
         * @var null|bool|Schema $additionalItems
963
         */
964 36
        $itemsLen = is_array($items) ? count($items) : 0;
965 36
        $index = 0;
966 4
        foreach ($result as $key => $value) {
967
            if ($index < $itemsLen) {
968 32
                $itemSchema = self::unboolSchema($items[$index]);
969 7
                $result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
970
            } else {
971 25
                if ($additionalItems instanceof SchemaContract) {
972 2
                    $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
973
                        . '[' . $index . ']:' . $index);
974 25
                } elseif (!$options->skipValidation && $additionalItems === false) {
975 25
                    $this->fail(new ArrayException('Unexpected array item'), $path);
976
                }
977 25
            }
978 17
            ++$index;
979 17
        }
980 21
981
        if (!$options->skipValidation && $this->uniqueItems) {
982
            if (!UniqueItems::isValid($data)) {
983 25
                $this->fail(new ArrayException('Array is not unique'), $path);
984 8
            }
985
        }
986
987 1165
        if (!$options->skipValidation && $this->contains !== null) {
988
            /** @var Schema|bool $contains */
989
            $contains = $this->contains;
990
            if ($contains === false) {
991
                $this->fail(new ArrayException('Contains is false'), $path);
992
            }
993
            if ($count === 0) {
994
                $this->fail(new ArrayException('Empty array fails contains constraint'), $path);
995
            }
996
            if ($contains === true) {
997 20
                $contains = self::unboolSchema($contains);
998
            }
999
            $containsOk = false;
1000 20
            foreach ($data as $key => $item) {
1001 10
                try {
1002
                    $contains->process($item, $options, $path . '->' . $key);
1003 10
                    $containsOk = true;
1004
                    break;
1005 4
                } catch (InvalidValue $exception) {
1006 4
                }
1007
            }
1008 10
            if (!$containsOk) {
1009
                $this->fail(new ArrayException('Array fails contains constraint'), $path);
1010
            }
1011
        }
1012
        return $result;
1013
    }
1014
1015
    /**
1016
     * @param mixed|string $data
1017
     * @param Context $options
1018
     * @param string $path
1019
     * @return bool|mixed|string
1020
     * @throws InvalidValue
1021 3316
     */
1022
    private function processContent($data, Context $options, $path)
1023 3316
    {
1024 3316
        try {
1025
            if ($options->unpackContentMediaType) {
1026 3316
                return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import);
1027 2
            } else {
1028
                Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true);
1029
            }
1030 3316
        } catch (InvalidValue $exception) {
1031 808
            $this->fail($exception, $path);
1032
        }
1033 808
        return $data;
1034 794
    }
1035 794
1036 6
    /**
1037 6
     * @param mixed $data
1038
     * @param Context $options
1039 6
     * @param string $path
1040
     * @param mixed|null $result
1041
     * @return array|mixed|null|object|\stdClass
1042 794
     * @throws InvalidValue
1043
     * @throws \Exception
1044
     * @throws \Swaggest\JsonDiff\Exception
1045 808
     */
1046 6
    public function process($data, Context $options, $path = '#', $result = null)
1047
    {
1048 808
        $origData = $data;
1049 6
        $import = $options->import;
1050 6
1051 6
        if (!$import && $data instanceof SchemaExporter) {
1052 6
            $data = $data->exportSchema(); // Used to export ClassStructure::schema()
1053 6
        }
1054 6
1055 6
        if (!$import && $data instanceof ObjectItemContract) {
1056
            $result = new \stdClass();
1057
1058 6
            if ('#' === $path) {
1059 6
                $injectDefinitions = new ScopeExit(function () use ($result, $options) {
1060 2
                    foreach ($options->exportedDefinitions as $ref => $data) {
1061 2
                        if ($data !== null && ($ref[0] === '#' || $ref[1] === '/')) {
1062 2
                            JsonPointer::add($result, JsonPointer::splitPath($ref), $data,
1063 2
                                /*JsonPointer::SKIP_IF_ISSET + */
1064 2
                                JsonPointer::RECURSIVE_KEY_CREATION);
1065
                        }
1066
                    }
1067
                });
1068 6
            }
1069 6
1070
            if ($options->isRef) {
1071
                $options->isRef = false;
1072
            } else {
1073 808
                if ('#' !== $path && $refs = $data->getFromRefs()) {
1074
                    $ref = $refs[0];
1075 1
                    if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1076 1
                        $exported = null;
1077 1
                        $options->exportedDefinitions[$ref] = &$exported;
1078
                        $options->isRef = true;
1079 808
                        $exported = $this->process($data, $options, $ref);
1080
                        unset($exported);
1081 808
                    }
1082
1083
                    $countRefs = count($refs);
1084 3316
                    for ($i = 1; $i < $countRefs; $i++) {
1085
                        $ref = $refs[$i];
1086 3316
                        if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1087 1
                            $exported = new \stdClass();
1088
                            $exported->{self::PROP_REF} = $refs[$i - 1];
1089
                            $options->exportedDefinitions[$ref] = $exported;
1090 3316
                        }
1091
                    }
1092
1093
                    $result->{self::PROP_REF} = $refs[$countRefs - 1];
1094 3316
                    return $result;
1095 3316
                }
1096
            }
1097
1098 3316
            if ($options->circularReferences->contains($data)) {
1099 1450
                /** @noinspection PhpIllegalArrayKeyTypeInspection */
1100
                $path = $options->circularReferences[$data];
1101
                $result->{self::PROP_REF} = PointerUtil::getDataPointer($path, true);
1102 3278
                return $result;
1103 3257
            }
1104
            $options->circularReferences->attach($data, $path);
1105
1106 3277
            $data = $data->jsonSerialize();
1107 1513
        }
1108
1109
        $path .= $this->getFromRefPath();
1110 3277
1111 43
        if (!$import && is_array($data) && $this->useObjectAsArray) {
1112
            $data = (object)$data;
1113
        }
1114 3277
1115 70
        if (null !== $options->dataPreProcessor) {
1116
            $data = $options->dataPreProcessor->process($data, $this, $import);
1117
        }
1118 3277
1119 2331
        if ($options->skipValidation) {
1120
            goto skipValidation;
1121
        }
1122 3277
1123 1090
        if ($this->type !== null) {
1124
            $this->processType($data, $options, $path);
1125
        }
1126 3276
1127 26
        if ($this->enum !== null) {
1128
            $this->processEnum($data, $path);
1129
        }
1130
1131
        if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) {
1132 3314
            $this->processConst($data, $path);
1133 107
        }
1134
1135
        if ($this->not !== null) {
1136 3314
            $this->processNot($data, $options, $path);
1137 1763
        }
1138
1139
        if (is_string($data)) {
1140 3314
            $this->processString($data, $path);
1141 354
        }
1142
1143
        if (is_int($data) || is_float($data)) {
1144 3314
            $this->processNumeric($data, $path);
1145 3269
        }
1146
1147
        if ($this->if !== null) {
1148 3311
            $result = $this->processIf($data, $options, $path);
1149 1241
        }
1150
1151
        skipValidation:
1152 3311
1153 20
        if ($result === null) {
1154 6
            $result = $data;
1155
        }
1156 20
1157
        if ($this->oneOf !== null) {
1158
            $result = $this->processOneOf($data, $options, $path);
1159 3311
        }
1160
1161
        if ($this->anyOf !== null) {
1162
            $result = $this->processAnyOf($data, $options, $path);
1163
        }
1164
1165
        if ($this->allOf !== null) {
1166 1
            $result = $this->processAllOf($data, $options, $path);
1167
        }
1168 1
1169 1
        if (is_object($data)) {
1170
            $result = $this->processObject($data, $options, $path, $result);
1171
        }
1172
1173
        if (is_array($data)) {
1174
            $result = $this->processArray($data, $options, $path, $result);
0 ignored issues
show
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

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