Completed
Push — master ( dfe67f...98ffcb )
by Viacheslav
12s
created

Schema::getNestedPropertyNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
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
29
/**
30
 * Class Schema
31
 * @package Swaggest\JsonSchema
32
 */
33
class Schema extends JsonSchema implements MetaHolder, SchemaContract
34
{
35
    const ENUM_NAMES_PROPERTY = 'x-enum-names';
36
    const CONST_PROPERTY = 'const';
37
38
    const DEFAULT_MAPPING = 'default';
39
40
    const VERSION_AUTO = 'a';
41
    const VERSION_DRAFT_04 = 4;
42
    const VERSION_DRAFT_06 = 6;
43
    const VERSION_DRAFT_07 = 7;
44
45
    const PROP_REF = '$ref';
46
    const PROP_ID = '$id';
47
    const PROP_ID_D4 = 'id';
48
49
    // Object
50
    /** @var null|Properties|Schema[]|Schema */
51
    public $properties;
52
    /** @var Schema|bool */
53
    public $additionalProperties;
54
    /** @var Schema[]|Properties */
55
    public $patternProperties;
56
    /** @var string[][]|Schema[]|\stdClass */
57
    public $dependencies;
58
59
    // Array
60
    /** @var null|Schema|Schema[] */
61
    public $items;
62
    /** @var null|Schema|bool */
63
    public $additionalItems;
64
65
    /** @var Schema[] */
66
    public $allOf;
67
    /** @var Schema */
68
    public $not;
69
    /** @var Schema[] */
70
    public $anyOf;
71
    /** @var Schema[] */
72
    public $oneOf;
73
74
    /** @var Schema */
75
    public $if;
76
    /** @var Schema */
77
    public $then;
78
    /** @var Schema */
79
    public $else;
80
81
82
    public $objectItemClass;
83
    private $useObjectAsArray = false;
84
85
    private $__dataToProperty = array();
86
    private $__propertyToData = array();
87
88
    private $__booleanSchema;
89
90 3
    public function addPropertyMapping($dataName, $propertyName, $mapping = self::DEFAULT_MAPPING)
91
    {
92 3
        $this->__dataToProperty[$mapping][$dataName] = $propertyName;
93 3
        $this->__propertyToData[$mapping][$propertyName] = $dataName;
94
95 3
        if ($mapping === self::DEFAULT_MAPPING && $this->properties instanceof Properties) {
96 3
            $this->properties->__defaultMapping[$propertyName] = $dataName;
0 ignored issues
show
Bug Best Practice introduced by
The property __defaultMapping does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
97
        }
98 3
        return $this;
99
    }
100
101
    /**
102
     * @param mixed $data
103
     * @param Context|null $options
104
     * @return SchemaContract
105
     * @throws Exception
106
     * @throws InvalidValue
107
     * @throws \Exception
108
     */
109 3189
    public static function import($data, Context $options = null)
110
    {
111 3189
        if (null === $options) {
112 25
            $options = new Context();
113
        }
114
115 3189
        $options->applyDefaults = false;
116
117 3189
        if (isset($options->schemasCache) && is_object($data)) {
118
            if ($options->schemasCache->contains($data)) {
119
                return $options->schemasCache->offsetGet($data);
120
            } else {
121
                $schema = parent::import($data, $options);
122
                $options->schemasCache->attach($data, $schema);
123
                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...
124
            }
125
        }
126
127
        // string $data is expected to be $ref uri
128 3189
        if (is_string($data)) {
129 4
            $data = (object)array(self::PROP_REF => $data);
130
        }
131
132 3189
        $data = self::unboolSchema($data);
133 3189
        if ($data instanceof SchemaContract) {
134 72
            return $data;
135
        }
136
137 3117
        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...
138
    }
139
140
    /**
141
     * @param mixed $data
142
     * @param Context|null $options
143
     * @return array|mixed|null|object|\stdClass
144
     * @throws Exception
145
     * @throws InvalidValue
146
     * @throws \Exception
147
     */
148 3211
    public function in($data, Context $options = null)
149
    {
150 3211
        if (null !== $this->__booleanSchema) {
151 72
            if ($this->__booleanSchema) {
152 36
                return $data;
153 36
            } elseif (empty($options->skipValidation)) {
154 18
                $this->fail(new InvalidValue('Denied by false schema'), '#');
155
            }
156
        }
157
158 3157
        if ($options === null) {
159 59
            $options = new Context();
160
        }
161
162 3157
        $options->import = true;
163
164 3157
        if ($options->refResolver === null) {
165 2838
            $options->refResolver = new RefResolver($data);
166
        } else {
167 1701
            $options->refResolver->setRootData($data);
168
        }
169
170 3157
        if ($options->remoteRefProvider) {
171 3090
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
172
        }
173
174 3157
        $options->refResolver->preProcessReferences($data, $options);
175
176 3157
        return $this->process($data, $options, '#');
177
    }
178
179
180
    /**
181
     * @param mixed $data
182
     * @param Context|null $options
183
     * @return array|mixed|null|object|\stdClass
184
     * @throws InvalidValue
185
     * @throws \Exception
186
     */
187 2373
    public function out($data, Context $options = null)
188
    {
189 2373
        if ($options === null) {
190 964
            $options = new Context();
191
        }
192
193 2373
        $options->circularReferences = new \SplObjectStorage();
194 2373
        $options->import = false;
195 2373
        return $this->process($data, $options);
196
    }
197
198
    /**
199
     * @param mixed $data
200
     * @param Context $options
201
     * @param string $path
202
     * @throws InvalidValue
203
     * @throws \Exception
204
     */
205 3142
    private function processType($data, Context $options, $path = '#')
206
    {
207 3142
        if ($options->tolerateStrings && is_string($data)) {
208
            $valid = Type::readString($this->type, $data);
209
        } else {
210 3142
            $valid = Type::isValid($this->type, $data, $options->version);
211
        }
212 3142
        if (!$valid) {
213 681
            $this->fail(new TypeException(ucfirst(
214 681
                    implode(', ', is_array($this->type) ? $this->type : array($this->type))
215 681
                    . ' expected, ' . json_encode($data) . ' received')
216 681
            ), $path);
217
        }
218 3140
    }
219
220
    /**
221
     * @param mixed $data
222
     * @param string $path
223
     * @throws InvalidValue
224
     * @throws \Exception
225
     */
226 1480
    private function processEnum($data, $path = '#')
227
    {
228 1480
        $enumOk = false;
229 1480
        foreach ($this->enum as $item) {
230 1480
            if ($item === $data) {
231 1460
                $enumOk = true;
232 1460
                break;
233
            } else {
234 1416
                if (is_array($item) || is_object($item)) {
235 12
                    $diff = new JsonDiff($item, $data, JsonDiff::STOP_ON_DIFF);
236 12
                    if ($diff->getDiffCnt() === 0) {
237 3
                        $enumOk = true;
238 1416
                        break;
239
                    }
240
                }
241
            }
242
        }
243 1480
        if (!$enumOk) {
244 94
            $this->fail(new EnumException('Enum failed, enum: ' . json_encode($this->enum) . ', data: ' . json_encode($data)), $path);
245
        }
246 1463
    }
247
248
    /**
249
     * @param mixed $data
250
     * @param string $path
251
     * @throws InvalidValue
252
     * @throws \Swaggest\JsonDiff\Exception
253
     */
254 43
    private function processConst($data, $path)
255
    {
256 43
        if ($this->const !== $data) {
257 37
            if ((is_object($this->const) && is_object($data))
258 37
                || (is_array($this->const) && is_array($data))) {
259 15
                $diff = new JsonDiff($this->const, $data,
260 15
                    JsonDiff::STOP_ON_DIFF);
261 15
                if ($diff->getDiffCnt() != 0) {
262 15
                    $this->fail(new ConstException('Const failed'), $path);
263
                }
264
            } else {
265 22
                $this->fail(new ConstException('Const failed'), $path);
266
            }
267
        }
268 20
    }
269
270
    /**
271
     * @param mixed $data
272
     * @param Context $options
273
     * @param string $path
274
     * @throws InvalidValue
275
     * @throws \Exception
276
     * @throws \Swaggest\JsonDiff\Exception
277
     */
278 69
    private function processNot($data, Context $options, $path)
279
    {
280 69
        $exception = false;
281
        try {
282 69
            self::unboolSchema($this->not)->process($data, $options, $path . '->not');
283 16
        } catch (InvalidValue $exception) {
284
            // Expected exception
285
        }
286 69
        if ($exception === false) {
287 55
            $this->fail(new LogicException('Not ' . json_encode($this->not) . ' expected, ' . json_encode($data) . ' received'), $path);
288
        }
289 16
    }
290
291
    /**
292
     * @param string $data
293
     * @param string $path
294
     * @throws InvalidValue
295
     */
296 2243
    private function processString($data, $path)
297
    {
298 2243
        if ($this->minLength !== null) {
299 38
            if (mb_strlen($data, 'UTF-8') < $this->minLength) {
300 9
                $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
301
            }
302
        }
303 2237
        if ($this->maxLength !== null) {
304 43
            if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
305 19
                $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
306
            }
307
        }
308 2234
        if ($this->pattern !== null) {
309 18
            if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
310 4
                $this->fail(new StringException(json_encode($data) . ' does not match to '
311 4
                    . $this->pattern, StringException::PATTERN_MISMATCH), $path);
312
            }
313
        }
314 2234
        if ($this->format !== null) {
315 409
            $validationError = Format::validationError($this->format, $data);
316 409
            if ($validationError !== null) {
317 137
                if (!($this->format === "uri" && substr($path, -3) === ':id')) {
318 122
                    $this->fail(new StringException($validationError), $path);
319
                }
320
            }
321
        }
322 2234
    }
323
324
    /**
325
     * @param float|int $data
326
     * @param string $path
327
     * @throws InvalidValue
328
     */
329 1048
    private function processNumeric($data, $path)
330
    {
331 1048
        if ($this->multipleOf !== null) {
332 39
            $div = $data / $this->multipleOf;
333 39
            if ($div != (int)$div) {
334 15
                $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
335
            }
336
        }
337
338 1048
        if ($this->exclusiveMaximum !== null && !is_bool($this->exclusiveMaximum)) {
339 32
            if ($data >= $this->exclusiveMaximum) {
340 18
                $this->fail(new NumericException(
341 18
                    'Value less or equal than ' . $this->exclusiveMaximum . ' expected, ' . $data . ' received',
342 18
                    NumericException::MAXIMUM), $path);
343
            }
344
        }
345
346 1048
        if ($this->exclusiveMinimum !== null && !is_bool($this->exclusiveMinimum)) {
347 24
            if ($data <= $this->exclusiveMinimum) {
348 12
                $this->fail(new NumericException(
349 12
                    'Value more or equal than ' . $this->exclusiveMinimum . ' expected, ' . $data . ' received',
350 12
                    NumericException::MINIMUM), $path);
351
            }
352
        }
353
354 1048
        if ($this->maximum !== null) {
355 43
            if ($this->exclusiveMaximum === true) {
356 3
                if ($data >= $this->maximum) {
357 2
                    $this->fail(new NumericException(
358 2
                        'Value less or equal than ' . $this->maximum . ' expected, ' . $data . ' received',
359 3
                        NumericException::MAXIMUM), $path);
360
                }
361
            } else {
362 40
                if ($data > $this->maximum) {
363 13
                    $this->fail(new NumericException(
364 13
                        'Value less than ' . $this->minimum . ' expected, ' . $data . ' received',
365 13
                        NumericException::MAXIMUM), $path);
366
                }
367
            }
368
        }
369
370 1048
        if ($this->minimum !== null) {
371 516
            if ($this->exclusiveMinimum === true) {
372 93
                if ($data <= $this->minimum) {
373 2
                    $this->fail(new NumericException(
374 2
                        'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
375 93
                        NumericException::MINIMUM), $path);
376
                }
377
            } else {
378 441
                if ($data < $this->minimum) {
379 43
                    $this->fail(new NumericException(
380 43
                        'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
381 43
                        NumericException::MINIMUM), $path);
382
                }
383
            }
384
        }
385 1047
    }
386
387
    /**
388
     * @param mixed $data
389
     * @param Context $options
390
     * @param string $path
391
     * @return array|mixed|null|object|\stdClass
392
     * @throws InvalidValue
393
     * @throws \Exception
394
     * @throws \Swaggest\JsonDiff\Exception
395
     */
396 105
    private function processOneOf($data, Context $options, $path)
397
    {
398 105
        $successes = 0;
399 105
        $failures = '';
400 105
        $subErrors = [];
401 105
        $skipValidation = false;
402 105
        if ($options->skipValidation) {
403 41
            $skipValidation = true;
404 41
            $options->skipValidation = false;
405
        }
406
407 105
        $result = $data;
408 105
        foreach ($this->oneOf as $index => $item) {
409
            try {
410 105
                $result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf[' . $index . ']');
411 83
                $successes++;
412 83
                if ($successes > 1 || $options->skipValidation) {
413 83
                    break;
414
                }
415 73
            } catch (InvalidValue $exception) {
416 73
                $subErrors[$index] = $exception;
417 105
                $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
418
                // Expected exception
419
            }
420
        }
421 105
        if ($skipValidation) {
422 41
            $options->skipValidation = true;
423 41
            if ($successes === 0) {
424 8
                $result = self::unboolSchema($this->oneOf[0])->process($data, $options, $path . '->oneOf[0]');
425
            }
426
        }
427
428 105
        if (!$options->skipValidation) {
429 64
            if ($successes === 0) {
430 19
                $exception = new LogicException('No valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}");
431 19
                $exception->error = 'No valid results for oneOf';
432 19
                $exception->subErrors = $subErrors;
433 19
                $this->fail($exception, $path);
434 50
            } elseif ($successes > 1) {
435 17
                $exception = new LogicException('More than 1 valid result for oneOf: '
436 17
                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf {'
437 17
                    . "\n" . substr($failures, 0, -1) . "\n}");
438 17
                $exception->error = 'More than 1 valid result for oneOf';
439 17
                $exception->subErrors = $subErrors;
440 17
                $this->fail($exception, $path);
441
            }
442
        }
443 75
        return $result;
444
    }
445
446
    /**
447
     * @param mixed $data
448
     * @param Context $options
449
     * @param string $path
450
     * @return array|mixed|null|object|\stdClass
451
     * @throws InvalidValue
452
     * @throws \Exception
453
     * @throws \Swaggest\JsonDiff\Exception
454
     */
455 1718
    private function processAnyOf($data, Context $options, $path)
456
    {
457 1718
        $successes = 0;
458 1718
        $failures = '';
459 1718
        $subErrors = [];
460 1718
        $result = $data;
461 1718
        foreach ($this->anyOf as $index => $item) {
462
            try {
463 1718
                $result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf[' . $index . ']');
464 1712
                $successes++;
465 1712
                if ($successes) {
466 1712
                    break;
467
                }
468 430
            } catch (InvalidValue $exception) {
469 430
                $subErrors[$index] = $exception;
470 430
                $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
471
                // Expected exception
472
            }
473
        }
474 1718
        if (!$successes && !$options->skipValidation) {
475 29
            $exception = new LogicException('No valid results for anyOf {' . "\n"
476 29
                . substr(Helper::padLines(' ', $failures, false), 0, -1)
477 29
                . "\n}");
478 29
            $exception->error = 'No valid results for anyOf';
479 29
            $exception->subErrors = $subErrors;
480 29
            $this->fail($exception, $path);
481
        }
482 1712
        return $result;
483
    }
484
485
    /**
486
     * @param mixed $data
487
     * @param Context $options
488
     * @param string $path
489
     * @return array|mixed|null|object|\stdClass
490
     * @throws InvalidValue
491
     * @throws \Exception
492
     * @throws \Swaggest\JsonDiff\Exception
493
     */
494 354
    private function processAllOf($data, Context $options, $path)
495
    {
496 354
        $result = $data;
497 354
        foreach ($this->allOf as $index => $item) {
498 354
            $result = self::unboolSchema($item)->process($data, $options, $path . '->allOf[' . $index . ']');
499
        }
500 316
        return $result;
501
    }
502
503
    /**
504
     * @param mixed $data
505
     * @param Context $options
506
     * @param string $path
507
     * @return array|mixed|null|object|\stdClass
508
     * @throws InvalidValue
509
     * @throws \Exception
510
     * @throws \Swaggest\JsonDiff\Exception
511
     */
512 26
    private function processIf($data, Context $options, $path)
513
    {
514 26
        $valid = true;
515
        try {
516 26
            self::unboolSchema($this->if)->process($data, $options, $path . '->if');
517 13
        } catch (InvalidValue $exception) {
518 13
            $valid = false;
519
        }
520 26
        if ($valid) {
521 18
            if ($this->then !== null) {
522 18
                return self::unboolSchema($this->then)->process($data, $options, $path . '->then');
523
            }
524
        } else {
525 13
            if ($this->else !== null) {
526 6
                return self::unboolSchema($this->else)->process($data, $options, $path . '->else');
527
            }
528
        }
529 10
        return null;
530
    }
531
532
    /**
533
     * @param \stdClass $data
534
     * @param Context $options
535
     * @param string $path
536
     * @throws InvalidValue
537
     */
538 161
    private function processObjectRequired($data, Context $options, $path)
539
    {
540 161
        if (isset($this->__dataToProperty[$options->mapping])) {
541 2
            if ($options->import) {
542 1
                foreach ($this->required as $item) {
543 1
                    if (isset($this->__propertyToData[$options->mapping][$item])) {
544 1
                        $item = $this->__propertyToData[$options->mapping][$item];
545
                    }
546 1
                    if (!property_exists($data, $item)) {
547 1
                        $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($data, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
548
                    }
549
                }
550
            } else {
551 2
                foreach ($this->required as $item) {
552 2
                    if (isset($this->__dataToProperty[$options->mapping][$item])) {
553
                        $item = $this->__dataToProperty[$options->mapping][$item];
554
                    }
555 2
                    if (!property_exists($data, $item)) {
556 2
                        $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($data, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
557
                    }
558
                }
559
            }
560
561
        } else {
562 160
            foreach ($this->required as $item) {
563 156
                if (!property_exists($data, $item)) {
564 156
                    $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($data, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
565
                }
566
            }
567
        }
568 129
    }
569
570
    /**
571
     * @param \stdClass $data
572
     * @param Context $options
573
     * @param string $path
574
     * @param ObjectItemContract|null $result
575
     * @return array|null|ClassStructure|ObjectItemContract
576
     * @throws InvalidValue
577
     * @throws \Exception
578
     * @throws \Swaggest\JsonDiff\Exception
579
     */
580 3155
    private function processObject($data, Context $options, $path, $result = null)
581
    {
582 3155
        $import = $options->import;
583
584 3155
        if (!$options->skipValidation && $this->required !== null) {
585 161
            $this->processObjectRequired($data, $options, $path);
586
        }
587
588 3154
        if ($import) {
589 3139
            if (!$options->validateOnly) {
590
591 3139
                if ($this->useObjectAsArray) {
592 1
                    $result = array();
593 3138
                } elseif (!$result instanceof ObjectItemContract) {
594
                    //* todo check performance impact
595 3138
                    if (null === $this->objectItemClass) {
596 1157
                        $result = new ObjectItem();
597
                    } else {
598 3127
                        $className = $this->objectItemClass;
599 3127
                        if ($options->objectItemClassMapping !== null) {
600
                            if (isset($options->objectItemClassMapping[$className])) {
601
                                $className = $options->objectItemClassMapping[$className];
602
                            }
603
                        }
604 3127
                        $result = new $className;
605
                    }
606
                    //*/
607
608
609 3138
                    if ($result instanceof ClassStructure) {
610 3126
                        if ($result->__validateOnSet) {
0 ignored issues
show
Bug Best Practice introduced by
The property __validateOnSet does not exist on Swaggest\JsonSchema\Structure\ObjectItem. Since you implemented __get, consider adding a @property annotation.
Loading history...
611 3126
                            $result->__validateOnSet = false;
0 ignored issues
show
Bug Best Practice introduced by
The property __validateOnSet does not exist on Swaggest\JsonSchema\Structure\ObjectItem. Since you implemented __set, consider adding a @property annotation.
Loading history...
612
                            /** @noinspection PhpUnusedLocalVariableInspection */
613
                            /* todo check performance impact
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
614
                            $validateOnSetHandler = new ScopeExit(function () use ($result) {
615
                                $result->__validateOnSet = true;
616
                            });
617
                            //*/
618
                        }
619
                    }
620
621
                    //* todo check performance impact
622 3138
                    if ($result instanceof ObjectItemContract) {
623 3138
                        $result->setDocumentPath($path);
624
                    }
625
                    //*/
626
                }
627
            }
628
        }
629
630
        // @todo better check for schema id
631
632 3154
        if ($import
633 3154
            && isset($data->{Schema::PROP_ID_D4})
634 3154
            && ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO)
635 3154
            && is_string($data->{Schema::PROP_ID_D4})) {
636 35
            $id = $data->{Schema::PROP_ID_D4};
637 35
            $refResolver = $options->refResolver;
638 35
            $parentScope = $refResolver->updateResolutionScope($id);
639
            /** @noinspection PhpUnusedLocalVariableInspection */
640
            $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...
641 35
                $refResolver->setResolutionScope($parentScope);
642 35
            });
643
        }
644
645 3154
        if ($import
646 3154
            && isset($data->{self::PROP_ID})
647 3154
            && ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO)
648 3154
            && is_string($data->{self::PROP_ID})) {
649 114
            $id = $data->{self::PROP_ID};
650 114
            $refResolver = $options->refResolver;
651 114
            $parentScope = $refResolver->updateResolutionScope($id);
652
            /** @noinspection PhpUnusedLocalVariableInspection */
653
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
654 114
                $refResolver->setResolutionScope($parentScope);
655 114
            });
656
        }
657
658 3154
        if ($import) {
659
            try {
660
661 3139
                $refProperty = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $refProperty is dead and can be removed.
Loading history...
662 3139
                $dereference = true;
663
664 3139
                if (isset($data->{self::PROP_REF})) {
665 432
                    if (null === $refProperty = $this->properties[self::PROP_REF]) {
666 432
                        if (isset($this->__dataToProperty[$options->mapping][self::PROP_REF])) {
667 420
                            $refProperty = $this->properties[$this->__dataToProperty[$options->mapping][self::PROP_REF]];
668
                        }
669
                    }
670
671 432
                    if (isset($refProperty) && ($refProperty->format !== Format::URI_REFERENCE)) {
672 14
                        $dereference = false;
673
                    }
674
                }
675
676
                if (
677 3139
                    isset($data->{self::PROP_REF})
678 3139
                    && is_string($data->{self::PROP_REF})
679 3139
                    && $dereference
680
                ) {
681 420
                    $refString = $data->{self::PROP_REF};
682
683
                    // todo check performance impact
684 420
                    if ($refString === 'http://json-schema.org/draft-04/schema#'
685 410
                        || $refString === 'http://json-schema.org/draft-06/schema#'
686 420
                        || $refString === 'http://json-schema.org/draft-07/schema#') {
687 26
                        return Schema::schema();
688
                    }
689
690
                    // TODO consider process # by reference here ?
691 394
                    $refResolver = $options->refResolver;
692 394
                    $preRefScope = $refResolver->getResolutionScope();
693
                    /** @noinspection PhpUnusedLocalVariableInspection */
694
                    $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...
695 394
                        $refResolver->setResolutionScope($preRefScope);
696 394
                    });
697
698 394
                    $ref = $refResolver->resolveReference($refString);
699 394
                    $data = self::unboolSchemaData($ref->getData());
700 394
                    if (!$options->validateOnly) {
701 394
                        if ($ref->isImported()) {
702 175
                            $refResult = $ref->getImported();
703 175
                            return $refResult;
704
                        }
705 394
                        $ref->setImported($result);
706
                        try {
707 394
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString, $result);
708 394
                            if ($refResult instanceof ObjectItemContract) {
709 394
                                if ($refResult->getFromRefs()) {
710 51
                                    $refResult = clone $refResult; // @todo check performance, consider option
711
                                }
712 394
                                $refResult->setFromRef($refString);
713
                            }
714 394
                            $ref->setImported($refResult);
715 1
                        } catch (InvalidValue $exception) {
716 1
                            $ref->unsetImported();
717 1
                            throw $exception;
718
                        }
719 394
                        return $refResult;
720
                    } else {
721 3139
                        $this->process($data, $options, $path . '->$ref:' . $refString);
722
                    }
723
                }
724 1
            } catch (InvalidValue $exception) {
725 1
                $this->fail($exception, $path);
726
            }
727
        }
728
729
        /** @var Schema[]|null $properties */
730 3154
        $properties = null;
731
732 3154
        $nestedProperties = null;
733 3154
        if ($this->properties !== null) {
734 3140
            $properties = $this->properties->toArray(); // todo call directly
735 3140
            if ($this->properties instanceof Properties) {
736 3140
                $nestedProperties = $this->properties->nestedProperties;
0 ignored issues
show
Bug Best Practice introduced by
The property nestedProperties does not exist on Swaggest\JsonSchema\Schema. Since you implemented __get, consider adding a @property annotation.
Loading history...
737
            } else {
738 579
                $nestedProperties = array();
739
            }
740
        }
741
742 3154
        $array = array();
743 3154
        if (!empty($this->__dataToProperty[$options->mapping])) { // todo skip on $options->validateOnly
744 3122
            foreach ((array)$data as $key => $value) {
745 3118
                if ($import) {
746 3114
                    if (isset($this->__dataToProperty[$options->mapping][$key])) {
747 3114
                        $key = $this->__dataToProperty[$options->mapping][$key];
748
                    }
749
                } else {
750 25
                    if (isset($this->__propertyToData[$options->mapping][$key])) {
751 2
                        $key = $this->__propertyToData[$options->mapping][$key];
752
                    }
753
                }
754 3122
                $array[$key] = $value;
755
            }
756
        } else {
757 1237
            $array = (array)$data;
758
        }
759
760 3154
        if (!$options->skipValidation) {
761 3133
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
762 4
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
763
            }
764 3133
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
765 3
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
766
            }
767 3133
            if ($this->propertyNames !== null) {
768 17
                $propertyNames = self::unboolSchema($this->propertyNames);
769 17
                foreach ($array as $key => $tmp) {
770 10
                    $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

770
                    $propertyNames->/** @scrutinizer ignore-call */ 
771
                                    process($key, $options, $path . '->propertyNames:' . $key);
Loading history...
771
                }
772
            }
773
        }
774
775 3154
        $defaultApplied = array();
776 3154
        if ($import
777 3154
            && !$options->validateOnly
778 3154
            && $options->applyDefaults
779 3154
            && $properties !== null
780
        ) {
781 38
            foreach ($properties as $key => $property) {
782
                // todo check when property is \stdClass `{}` here (RefTest)
783 36
                if ($property instanceof SchemaContract && null !== $default = $property->getDefault()) {
784 6
                    if (isset($this->__dataToProperty[$options->mapping][$key])) {
785
                        $key = $this->__dataToProperty[$options->mapping][$key];
786
                    }
787 6
                    if (!array_key_exists($key, $array)) {
788 6
                        $defaultApplied[$key] = true;
789 36
                        $array[$key] = $default;
790
                    }
791
                }
792
            }
793
        }
794
795 3154
        foreach ($array as $key => $value) {
796 3144
            if ($key === '' && PHP_VERSION_ID < 71000) {
797 1
                $this->fail(new InvalidValue('Empty property name'), $path);
798
            }
799
800 3143
            $found = false;
801
802 3143
            if (!$options->skipValidation && !empty($this->dependencies)) {
803 73
                $deps = $this->dependencies;
804 73
                if (isset($deps->$key)) {
805 63
                    $dependencies = $deps->$key;
806 63
                    $dependencies = self::unboolSchema($dependencies);
807 63
                    if ($dependencies instanceof SchemaContract) {
808 29
                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
809
                    } else {
810 34
                        foreach ($dependencies as $item) {
811 31
                            if (!property_exists($data, $item)) {
812 18
                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
813 31
                                    ObjectException::DEPENDENCY_MISSING), $path);
814
                            }
815
                        }
816
                    }
817
                }
818
            }
819
820 3143
            $propertyFound = false;
821 3143
            if (isset($properties[$key])) {
822
                /** @var Schema[] $properties */
823 3133
                $prop = self::unboolSchema($properties[$key]);
824 3133
                $propertyFound = true;
825 3133
                $found = true;
826 3133
                if ($prop instanceof SchemaContract) {
827 3076
                    $value = $prop->process(
828 3076
                        $value,
829 3076
                        isset($defaultApplied[$key]) ? $options->withDefault() : $options,
830 3076
                        $path . '->properties:' . $key
831
                    );
832
                }
833
            }
834
835
            /** @var Egg[] $nestedEggs */
836 3138
            $nestedEggs = null;
837 3138
            if (isset($nestedProperties[$key])) {
838 6
                $found = true;
839 6
                $nestedEggs = $nestedProperties[$key];
840
                // todo iterate all nested props?
841 6
                $value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key);
842
            }
843
844 3138
            if ($this->patternProperties !== null) {
845 166
                foreach ($this->patternProperties as $pattern => $propertySchema) {
846 166
                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
847 120
                        $found = true;
848 120
                        $value = self::unboolSchema($propertySchema)->process($value, $options,
849 120
                            $path . '->patternProperties[' . strtr($pattern, array('~' => '~1', ':' => '~2')) . ']:' . $key);
850 93
                        if (!$options->validateOnly && $import) {
851 144
                            $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

851
                            $result->/** @scrutinizer ignore-call */ 
852
                                     addPatternPropertyName($pattern, $key);
Loading history...
852
                        }
853
                        //break; // todo manage multiple import data properly (pattern accessor)
854
                    }
855
                }
856
            }
857 3138
            if (!$found && $this->additionalProperties !== null) {
858 997
                if (!$options->skipValidation && $this->additionalProperties === false) {
859 12
                    $this->fail(new ObjectException('Additional properties not allowed: ' . $key), $path);
860
                }
861
862 997
                if ($this->additionalProperties instanceof SchemaContract) {
863 997
                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
864
                }
865
866 990
                if ($import && !$this->useObjectAsArray && !$options->validateOnly) {
867 986
                    $result->addAdditionalPropertyName($key);
868
                }
869
            }
870
871 3134
            if (!$options->validateOnly && $nestedEggs && $import) {
872 5
                foreach ($nestedEggs as $nestedEgg) {
873 5
                    $result->setNestedProperty($key, $value, $nestedEgg);
874
                }
875 5
                if ($propertyFound) {
876 5
                    $result->$key = $value;
877
                }
878
            } else {
879 3133
                if ($this->useObjectAsArray && $import) {
880 1
                    $result[$key] = $value;
881
                } else {
882 3133
                    if ($found || !$import) {
883 3132
                        $result->$key = $value;
884 1142
                    } elseif (!isset($result->$key)) {
885 3134
                        $result->$key = $value;
886
                    }
887
                }
888
            }
889
        }
890
891 3142
        return $result;
892
    }
893
894
    /**
895
     * @param array $data
896
     * @param Context $options
897
     * @param string $path
898
     * @param array $result
899
     * @return mixed
900
     * @throws InvalidValue
901
     * @throws \Exception
902
     * @throws \Swaggest\JsonDiff\Exception
903
     */
904 1217
    private function processArray($data, Context $options, $path, $result)
905
    {
906 1217
        $count = count($data);
907 1217
        if (!$options->skipValidation) {
908 1017
            if ($this->minItems !== null && $count < $this->minItems) {
909 9
                $this->fail(new ArrayException("Not enough items in array"), $path);
910
            }
911
912 1011
            if ($this->maxItems !== null && $count > $this->maxItems) {
913 6
                $this->fail(new ArrayException("Too many items in array"), $path);
914
            }
915
        }
916
917 1205
        $pathItems = 'items';
918 1205
        $this->items = self::unboolSchema($this->items);
919 1205
        if ($this->items instanceof SchemaContract) {
0 ignored issues
show
introduced by
$this->items is never a sub-type of Swaggest\JsonSchema\SchemaContract. If $this->items can have other possible types, add them to src/Schema.php:60.
Loading history...
920 852
            $items = array();
921 852
            $additionalItems = $this->items;
922 690
        } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
923 690
            $items = array();
924 690
            $additionalItems = true;
925
        } else { // listed items
926 104
            $items = $this->items;
927 104
            $additionalItems = $this->additionalItems;
928 104
            $pathItems = 'additionalItems';
929
        }
930
931
        /**
932
         * @var Schema|Schema[] $items
933
         * @var null|bool|Schema $additionalItems
934
         */
935 1205
        $itemsLen = is_array($items) ? count($items) : 0;
936 1205
        $index = 0;
937 1205
        foreach ($result as $key => $value) {
938 1080
            if ($index < $itemsLen) {
939 94
                $itemSchema = self::unboolSchema($items[$index]);
940 94
                $result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
941
            } else {
942 1080
                if ($additionalItems instanceof SchemaContract) {
943 826
                    $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
944 826
                        . '[' . $index . ']:' . $index);
945 574
                } elseif (!$options->skipValidation && $additionalItems === false) {
946 6
                    $this->fail(new ArrayException('Unexpected array item'), $path);
947
                }
948
            }
949 1060
            ++$index;
950
        }
951
952 1181
        if (!$options->skipValidation && $this->uniqueItems) {
953 508
            if (!UniqueItems::isValid($data)) {
954 19
                $this->fail(new ArrayException('Array is not unique'), $path);
955
            }
956
        }
957
958 1162
        if (!$options->skipValidation && $this->contains !== null) {
959
            /** @var Schema|bool $contains */
960 36
            $contains = $this->contains;
961 36
            if ($contains === false) {
962 4
                $this->fail(new ArrayException('Contains is false'), $path);
963
            }
964 32
            if ($count === 0) {
965 7
                $this->fail(new ArrayException('Empty array fails contains constraint'), $path);
966
            }
967 25
            if ($contains === true) {
968 2
                $contains = self::unboolSchema($contains);
969
            }
970 25
            $containsOk = false;
971 25
            foreach ($data as $key => $item) {
972
                try {
973 25
                    $contains->process($item, $options, $path . '->' . $key);
974 17
                    $containsOk = true;
975 17
                    break;
976 21
                } catch (InvalidValue $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
977
                }
978
            }
979 25
            if (!$containsOk) {
980 8
                $this->fail(new ArrayException('Array fails contains constraint'), $path);
981
            }
982
        }
983 1143
        return $result;
984
    }
985
986
    /**
987
     * @param mixed|string $data
988
     * @param Context $options
989
     * @param string $path
990
     * @return bool|mixed|string
991
     * @throws InvalidValue
992
     */
993 14
    private function processContent($data, Context $options, $path)
994
    {
995
        try {
996 14
            if ($options->unpackContentMediaType) {
997 7
                return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import);
998
            } else {
999 7
                Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true);
1000
            }
1001 4
        } catch (InvalidValue $exception) {
1002 4
            $this->fail($exception, $path);
1003
        }
1004 7
        return $data;
1005
    }
1006
1007
    /**
1008
     * @param mixed $data
1009
     * @param Context $options
1010
     * @param string $path
1011
     * @param mixed|null $result
1012
     * @return array|mixed|null|object|\stdClass
1013
     * @throws InvalidValue
1014
     * @throws \Exception
1015
     * @throws \Swaggest\JsonDiff\Exception
1016
     */
1017 3201
    public function process($data, Context $options, $path = '#', $result = null)
1018
    {
1019 3201
        $import = $options->import;
1020
1021 3201
        if (!$import && $data instanceof SchemaExporter) {
1022 2
            $data = $data->exportSchema(); // Used to export ClassStructure::schema()
1023
        }
1024
1025 3201
        if (!$import && $data instanceof ObjectItemContract) {
1026 789
            $result = new \stdClass();
1027
1028 789
            if ('#' === $path) {
1029 776
                $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...
1030 776
                    foreach ($options->exportedDefinitions as $ref => $data) {
1031 6
                        if ($data !== null) {
1032 6
                            JsonPointer::add($result, JsonPointer::splitPath($ref), $data,
1033
                                /*JsonPointer::SKIP_IF_ISSET + */
1034 6
                                JsonPointer::RECURSIVE_KEY_CREATION);
1035
                        }
1036
                    }
1037 776
                });
1038
            }
1039
1040 789
            if ($options->isRef) {
1041 6
                $options->isRef = false;
1042
            } else {
1043 789
                if ('#' !== $path && $refs = $data->getFromRefs()) {
1044 6
                    $ref = $refs[0];
1045 6
                    if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1046 6
                        $exported = null;
1047 6
                        $options->exportedDefinitions[$ref] = &$exported;
1048 6
                        $options->isRef = true;
1049 6
                        $exported = $this->process($data, $options, $ref);
1050 6
                        unset($exported);
1051
                    }
1052
1053 6
                    for ($i = 1; $i < count($refs); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Bug introduced by
It seems like $refs can also be of type string; however, parameter $var of count() does only seem to accept Countable|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

1053
                    for ($i = 1; $i < count(/** @scrutinizer ignore-type */ $refs); $i++) {
Loading history...
1054 2
                        $ref = $refs[$i];
1055 2
                        if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1056 2
                            $exported = new \stdClass();
1057 2
                            $exported->{self::PROP_REF} = $refs[$i - 1];
1058 2
                            $options->exportedDefinitions[$ref] = $exported;
1059
                        }
1060
                    }
1061
1062 6
                    $result->{self::PROP_REF} = $refs[count($refs) - 1];
1063 6
                    return $result;
1064
                }
1065
            }
1066
1067 789
            if ($options->circularReferences->contains($data)) {
1068
                /** @noinspection PhpIllegalArrayKeyTypeInspection */
1069 1
                $path = $options->circularReferences[$data];
1070 1
                $result->{self::PROP_REF} = PointerUtil::getDataPointer($path, true);
1071 1
                return $result;
1072
            }
1073 789
            $options->circularReferences->attach($data, $path);
1074
1075 789
            $data = $data->jsonSerialize();
1076
        }
1077
1078 3201
        $path .= $this->getFromRefPath();
1079
1080 3201
        if (!$import && is_array($data) && $this->useObjectAsArray) {
1081 1
            $data = (object)$data;
1082
        }
1083
1084 3201
        if (null !== $options->dataPreProcessor) {
1085
            $data = $options->dataPreProcessor->process($data, $this, $import);
1086
        }
1087
1088 3201
        if ($result === null) {
1089 3201
            $result = $data;
1090
        }
1091
1092 3201
        if ($options->skipValidation) {
1093 1416
            goto skipValidation;
1094
        }
1095
1096 3163
        if ($this->type !== null) {
1097 3142
            $this->processType($data, $options, $path);
1098
        }
1099
1100 3162
        if ($this->enum !== null) {
1101 1480
            $this->processEnum($data, $path);
1102
        }
1103
1104 3162
        if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) {
1105 43
            $this->processConst($data, $path);
1106
        }
1107
1108 3162
        if ($this->not !== null) {
1109 69
            $this->processNot($data, $options, $path);
1110
        }
1111
1112 3162
        if (is_string($data)) {
1113 2243
            $this->processString($data, $path);
1114
        }
1115
1116 3162
        if (is_int($data) || is_float($data)) {
1117 1048
            $this->processNumeric($data, $path);
1118
        }
1119
1120 3162
        if ($this->if !== null) {
1121 26
            $result = $this->processIf($data, $options, $path);
1122
        }
1123
1124
        skipValidation:
1125
1126 3200
        if ($this->oneOf !== null) {
1127 105
            $result = $this->processOneOf($data, $options, $path);
1128
        }
1129
1130 3200
        if ($this->anyOf !== null) {
1131 1718
            $result = $this->processAnyOf($data, $options, $path);
1132
        }
1133
1134 3200
        if ($this->allOf !== null) {
1135 354
            $result = $this->processAllOf($data, $options, $path);
1136
        }
1137
1138 3200
        if ($data instanceof \stdClass) {
1139 3155
            $result = $this->processObject($data, $options, $path, $result);
1140
        }
1141
1142 3197
        if (is_array($data)) {
1143 1217
            $result = $this->processArray($data, $options, $path, $result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type object and stdClass and Swaggest\JsonSchema\Schema and Swaggest\JsonSchema\Structure\ObjectItemContract and Swaggest\JsonSchema\Structure\ClassStructure; 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

1143
            $result = $this->processArray($data, $options, $path, /** @scrutinizer ignore-type */ $result);
Loading history...
1144
        }
1145
1146 3197
        if ($this->contentEncoding !== null || $this->contentMediaType !== null) {
1147 14
            $result = $this->processContent($data, $options, $path);
1148
        }
1149
1150 3197
        return $result;
1151
    }
1152
1153
    /**
1154
     * @param boolean $useObjectAsArray
1155
     * @return Schema
1156
     */
1157 1
    public function setUseObjectAsArray($useObjectAsArray)
1158
    {
1159 1
        $this->useObjectAsArray = $useObjectAsArray;
1160 1
        return $this;
1161
    }
1162
1163
    /**
1164
     * @param InvalidValue $exception
1165
     * @param string $path
1166
     * @throws InvalidValue
1167
     */
1168 1223
    private function fail(InvalidValue $exception, $path)
1169
    {
1170 1223
        if ($path !== '#') {
1171 756
            $exception->addPath($path);
1172
        }
1173 1223
        throw $exception;
1174
    }
1175
1176 13
    public static function integer()
1177
    {
1178 13
        $schema = new static();
1179 13
        $schema->type = Type::INTEGER;
1180 13
        return $schema;
1181
    }
1182
1183 4
    public static function number()
1184
    {
1185 4
        $schema = new static();
1186 4
        $schema->type = Type::NUMBER;
1187 4
        return $schema;
1188
    }
1189
1190 10
    public static function string()
1191
    {
1192 10
        $schema = new static();
1193 10
        $schema->type = Type::STRING;
1194 10
        return $schema;
1195
    }
1196
1197 4
    public static function boolean()
1198
    {
1199 4
        $schema = new static();
1200 4
        $schema->type = Type::BOOLEAN;
1201 4
        return $schema;
1202
    }
1203
1204 6
    public static function object()
1205
    {
1206 6
        $schema = new static();
1207 6
        $schema->type = Type::OBJECT;
1208 6
        return $schema;
1209
    }
1210
1211 1
    public static function arr()
1212
    {
1213 1
        $schema = new static();
1214 1
        $schema->type = Type::ARR;
1215 1
        return $schema;
1216
    }
1217
1218
    public static function null()
1219
    {
1220
        $schema = new static();
1221
        $schema->type = Type::NULL;
1222
        return $schema;
1223
    }
1224
1225
1226
    /**
1227
     * @param Properties $properties
1228
     * @return Schema
1229
     */
1230 3
    public function setProperties($properties)
1231
    {
1232 3
        $this->properties = $properties;
1233 3
        return $this;
1234
    }
1235
1236
    /**
1237
     * @param string $name
1238
     * @param Schema $schema
1239
     * @return $this
1240
     */
1241 3
    public function setProperty($name, $schema)
1242
    {
1243 3
        if (null === $this->properties) {
1244 3
            $this->properties = new Properties();
1245
        }
1246 3
        $this->properties->__set($name, $schema);
1247 3
        return $this;
1248
    }
1249
1250
    /**
1251
     * @param string $name
1252
     * @param SchemaContract $schema
1253
     * @return $this
1254
     * @throws Exception
1255
     */
1256
    public function setPatternProperty($name, $schema)
1257
    {
1258
        if (null === $this->patternProperties) {
1259
            $this->patternProperties = new Properties();
1260
        }
1261
        $this->patternProperties->__set($name, $schema);
1262
        return $this;
1263
    }
1264
1265
1266
    /** @var mixed[] */
1267
    private $metaItems = array();
1268
1269 1
    public function addMeta($meta, $name = null)
1270
    {
1271 1
        if ($name === null) {
1272 1
            $name = get_class($meta);
1273
        }
1274 1
        $this->metaItems[$name] = $meta;
1275 1
        return $this;
1276
    }
1277
1278 1
    public function getMeta($name)
1279
    {
1280 1
        if (isset($this->metaItems[$name])) {
1281 1
            return $this->metaItems[$name];
1282
        }
1283
        return null;
1284
    }
1285
1286
    /**
1287
     * @param Context $options
1288
     * @return ObjectItemContract
1289
     */
1290 5
    public function makeObjectItem(Context $options = null)
1291
    {
1292 5
        if (null === $this->objectItemClass) {
1293
            return new ObjectItem();
1294
        } else {
1295 5
            $className = $this->objectItemClass;
1296 5
            if ($options !== null) {
1297
                if (isset($options->objectItemClassMapping[$className])) {
1298
                    $className = $options->objectItemClassMapping[$className];
1299
                }
1300
            }
1301 5
            return new $className;
1302
        }
1303
    }
1304
1305
    /**
1306
     * @param mixed $schema
1307
     * @return mixed|Schema
1308
     */
1309 3213
    private static function unboolSchema($schema)
1310
    {
1311 3213
        static $trueSchema;
1312 3213
        static $falseSchema;
1313
1314 3213
        if (null === $trueSchema) {
1315 1
            $trueSchema = new Schema();
1316 1
            $trueSchema->__booleanSchema = true;
1317 1
            $falseSchema = new Schema();
1318 1
            $falseSchema->not = $trueSchema;
1319 1
            $falseSchema->__booleanSchema = false;
1320
        }
1321
1322 3213
        if ($schema === true) {
1323 108
            return $trueSchema;
1324 3185
        } elseif ($schema === false) {
1325 94
            return $falseSchema;
1326
        } else {
1327 3153
            return $schema;
1328
        }
1329
    }
1330
1331
    /**
1332
     * @param mixed $data
1333
     * @return \stdClass
1334
     */
1335 394
    private static function unboolSchemaData($data)
1336
    {
1337 394
        static $trueSchema;
1338 394
        static $falseSchema;
1339
1340 394
        if (null === $trueSchema) {
1341 1
            $trueSchema = new \stdClass();
1342 1
            $falseSchema = new \stdClass();
1343 1
            $falseSchema->not = $trueSchema;
1344
        }
1345
1346 394
        if ($data === true) {
1347 6
            return $trueSchema;
1348 390
        } elseif ($data === false) {
1349 5
            return $falseSchema;
1350
        } else {
1351 385
            return $data;
1352
        }
1353
    }
1354
1355 36
    public function getDefault()
1356
    {
1357 36
        return $this->default;
1358
    }
1359
1360 94
    public function getProperties()
1361
    {
1362 94
        return $this->properties;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->properties also could return the type Swaggest\JsonSchema\Schema which is incompatible with the return type mandated by Swaggest\JsonSchema\Sche...ntract::getProperties() of null|Swaggest\JsonSchema...a\Constraint\Properties.
Loading history...
1363
    }
1364
1365
    public function getObjectItemClass()
1366
    {
1367
        return $this->objectItemClass;
1368
    }
1369
1370
    /**
1371
     * @return string[]
1372
     */
1373 92
    public function getPropertyNames()
1374
    {
1375 92
        return array_keys($this->getProperties()->toArray());
1376
    }
1377
1378
    /**
1379
     * @return string[]
1380
     */
1381 92
    public function getNestedPropertyNames()
1382
    {
1383 92
        return $this->getProperties()->nestedPropertyNames;
1384
    }
1385
1386
}
1387