Passed
Pull Request — master (#38)
by Viacheslav
02:49
created

Schema::processObject()   F

Complexity

Conditions 99
Paths 10976

Size

Total Lines 309
Code Lines 183

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 169
CRAP Score 99.1208

Importance

Changes 0
Metric Value
dl 0
loc 309
ccs 169
cts 173
cp 0.9769
rs 2
c 0
b 0
f 0
cc 99
eloc 183
nc 10976
nop 4
crap 99.1208

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Swaggest\JsonSchema;
4
5
6
use PhpLang\ScopeExit;
7
use Swaggest\JsonDiff\JsonDiff;
8
use Swaggest\JsonDiff\JsonPointer;
9
use Swaggest\JsonSchema\Constraint\Content;
10
use Swaggest\JsonSchema\Constraint\Format;
11
use Swaggest\JsonSchema\Constraint\Properties;
12
use Swaggest\JsonSchema\Constraint\Type;
13
use Swaggest\JsonSchema\Constraint\UniqueItems;
14
use Swaggest\JsonSchema\Exception\ArrayException;
15
use Swaggest\JsonSchema\Exception\ConstException;
16
use Swaggest\JsonSchema\Exception\EnumException;
17
use Swaggest\JsonSchema\Exception\LogicException;
18
use Swaggest\JsonSchema\Exception\NumericException;
19
use Swaggest\JsonSchema\Exception\ObjectException;
20
use Swaggest\JsonSchema\Exception\StringException;
21
use Swaggest\JsonSchema\Exception\TypeException;
22
use Swaggest\JsonSchema\Meta\MetaHolder;
23
use Swaggest\JsonSchema\Path\PointerUtil;
24
use Swaggest\JsonSchema\Structure\ClassStructure;
25
use Swaggest\JsonSchema\Structure\Egg;
26
use Swaggest\JsonSchema\Structure\ObjectItem;
27
use Swaggest\JsonSchema\Structure\ObjectItemContract;
28
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 2
    public function addPropertyMapping($dataName, $propertyName, $mapping = self::DEFAULT_MAPPING)
91
    {
92 2
        $this->__dataToProperty[$mapping][$dataName] = $propertyName;
93 2
        $this->__propertyToData[$mapping][$propertyName] = $dataName;
94 2
        return $this;
95
    }
96
97
    /**
98
     * @param mixed $data
99
     * @param Context|null $options
100
     * @return SchemaContract
101
     * @throws Exception
102
     * @throws InvalidValue
103
     * @throws \Exception
104
     */
105 3187
    public static function import($data, Context $options = null)
106
    {
107 3187
        if (null === $options) {
108 24
            $options = new Context();
109
        }
110
111 3187
        $options->applyDefaults = false;
112
113 3187
        if (isset($options->schemasCache) && is_object($data)) {
114
            if ($options->schemasCache->contains($data)) {
115
                return $options->schemasCache->offsetGet($data);
116
            } else {
117
                $schema = parent::import($data, $options);
118
                $options->schemasCache->attach($data, $schema);
119
                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...
120
            }
121
        }
122
123
        // string $data is expected to be $ref uri
124 3187
        if (is_string($data)) {
125 4
            $data = (object)array(self::PROP_REF => $data);
126
        }
127
128 3187
        $data = self::unboolSchema($data);
129 3187
        if ($data instanceof SchemaContract) {
130 72
            return $data;
131
        }
132
133 3115
        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...
134
    }
135
136
    /**
137
     * @param mixed $data
138
     * @param Context|null $options
139
     * @return array|mixed|null|object|\stdClass
140
     * @throws Exception
141
     * @throws InvalidValue
142
     * @throws \Exception
143
     */
144 3209
    public function in($data, Context $options = null)
145
    {
146 3209
        if (null !== $this->__booleanSchema) {
147 72
            if ($this->__booleanSchema) {
148 36
                return $data;
149 36
            } elseif (empty($options->skipValidation)) {
150 18
                $this->fail(new InvalidValue('Denied by false schema'), '#');
151
            }
152
        }
153
154 3155
        if ($options === null) {
155 58
            $options = new Context();
156
        }
157
158 3155
        $options->import = true;
159
160 3155
        if ($options->refResolver === null) {
161 2836
            $options->refResolver = new RefResolver($data);
162
        } else {
163 1701
            $options->refResolver->setRootData($data);
164
        }
165
166 3155
        if ($options->remoteRefProvider) {
167 3090
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
168
        }
169
170 3155
        $options->refResolver->preProcessReferences($data, $options);
171
172 3155
        return $this->process($data, $options, '#');
173
    }
174
175
176
    /**
177
     * @param mixed $data
178
     * @param Context|null $options
179
     * @return array|mixed|null|object|\stdClass
180
     * @throws InvalidValue
181
     * @throws \Exception
182
     */
183 2370
    public function out($data, Context $options = null)
184
    {
185 2370
        if ($options === null) {
186 961
            $options = new Context();
187
        }
188
189 2370
        $options->circularReferences = new \SplObjectStorage();
190 2370
        $options->import = false;
191 2370
        return $this->process($data, $options);
192
    }
193
194
    /**
195
     * @param mixed $data
196
     * @param Context $options
197
     * @param string $path
198
     * @throws InvalidValue
199
     * @throws \Exception
200
     */
201 3138
    private function processType($data, Context $options, $path = '#')
202
    {
203 3138
        if ($options->tolerateStrings && is_string($data)) {
204
            $valid = Type::readString($this->type, $data);
205
        } else {
206 3138
            $valid = Type::isValid($this->type, $data, $options->version);
207
        }
208 3138
        if (!$valid) {
209 680
            $this->fail(new TypeException(ucfirst(
210 680
                    implode(', ', is_array($this->type) ? $this->type : array($this->type))
211 680
                    . ' expected, ' . json_encode($data) . ' received')
212 680
            ), $path);
213
        }
214 3136
    }
215
216
    /**
217
     * @param mixed $data
218
     * @param string $path
219
     * @throws InvalidValue
220
     * @throws \Exception
221
     */
222 1476
    private function processEnum($data, $path = '#')
223
    {
224 1476
        $enumOk = false;
225 1476
        foreach ($this->enum as $item) {
226 1476
            if ($item === $data) {
227 1456
                $enumOk = true;
228 1456
                break;
229
            } else {
230 1412
                if (is_array($item) || is_object($item)) {
231 12
                    $diff = new JsonDiff($item, $data, JsonDiff::STOP_ON_DIFF);
232 12
                    if ($diff->getDiffCnt() === 0) {
233 3
                        $enumOk = true;
234 1412
                        break;
235
                    }
236
                }
237
            }
238
        }
239 1476
        if (!$enumOk) {
240 94
            $this->fail(new EnumException('Enum failed, enum: ' . json_encode($this->enum) . ', data: ' . json_encode($data)), $path);
241
        }
242 1459
    }
243
244
    /**
245
     * @param mixed $data
246
     * @param string $path
247
     * @throws InvalidValue
248
     * @throws \Swaggest\JsonDiff\Exception
249
     */
250 43
    private function processConst($data, $path)
251
    {
252 43
        if ($this->const !== $data) {
253 37
            if ((is_object($this->const) && is_object($data))
254 37
                || (is_array($this->const) && is_array($data))) {
255 15
                $diff = new JsonDiff($this->const, $data,
256 15
                    JsonDiff::STOP_ON_DIFF);
257 15
                if ($diff->getDiffCnt() != 0) {
258 15
                    $this->fail(new ConstException('Const failed'), $path);
259
                }
260
            } else {
261 22
                $this->fail(new ConstException('Const failed'), $path);
262
            }
263
        }
264 20
    }
265
266
    /**
267
     * @param mixed $data
268
     * @param Context $options
269
     * @param string $path
270
     * @throws InvalidValue
271
     * @throws \Exception
272
     * @throws \Swaggest\JsonDiff\Exception
273
     */
274 69
    private function processNot($data, Context $options, $path)
275
    {
276 69
        $exception = false;
277
        try {
278 69
            self::unboolSchema($this->not)->process($data, $options, $path . '->not');
279 16
        } catch (InvalidValue $exception) {
280
            // Expected exception
281
        }
282 69
        if ($exception === false) {
283 55
            $this->fail(new LogicException('Not ' . json_encode($this->not) . ' expected, ' . json_encode($data) . ' received'), $path);
284
        }
285 16
    }
286
287
    /**
288
     * @param string $data
289
     * @param string $path
290
     * @throws InvalidValue
291
     */
292 2239
    private function processString($data, $path)
293
    {
294 2239
        if ($this->minLength !== null) {
295 38
            if (mb_strlen($data, 'UTF-8') < $this->minLength) {
296 9
                $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
297
            }
298
        }
299 2233
        if ($this->maxLength !== null) {
300 43
            if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
301 19
                $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
302
            }
303
        }
304 2230
        if ($this->pattern !== null) {
305 18
            if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
306 4
                $this->fail(new StringException(json_encode($data) . ' does not match to '
307 4
                    . $this->pattern, StringException::PATTERN_MISMATCH), $path);
308
            }
309
        }
310 2230
        if ($this->format !== null) {
311 409
            $validationError = Format::validationError($this->format, $data);
312 409
            if ($validationError !== null) {
313 137
                if (!($this->format === "uri" && substr($path, -3) === ':id')) {
314 122
                    $this->fail(new StringException($validationError), $path);
315
                }
316
            }
317
        }
318 2230
    }
319
320
    /**
321
     * @param float|int $data
322
     * @param string $path
323
     * @throws InvalidValue
324
     */
325 1047
    private function processNumeric($data, $path)
326
    {
327 1047
        if ($this->multipleOf !== null) {
328 39
            $div = $data / $this->multipleOf;
329 39
            if ($div != (int)$div) {
330 15
                $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
331
            }
332
        }
333
334 1047
        if ($this->exclusiveMaximum !== null && !is_bool($this->exclusiveMaximum)) {
335 32
            if ($data >= $this->exclusiveMaximum) {
336 18
                $this->fail(new NumericException(
337 18
                    'Value less or equal than ' . $this->exclusiveMaximum . ' expected, ' . $data . ' received',
338 18
                    NumericException::MAXIMUM), $path);
339
            }
340
        }
341
342 1047
        if ($this->exclusiveMinimum !== null && !is_bool($this->exclusiveMinimum)) {
343 24
            if ($data <= $this->exclusiveMinimum) {
344 12
                $this->fail(new NumericException(
345 12
                    'Value more or equal than ' . $this->exclusiveMinimum . ' expected, ' . $data . ' received',
346 12
                    NumericException::MINIMUM), $path);
347
            }
348
        }
349
350 1047
        if ($this->maximum !== null) {
351 43
            if ($this->exclusiveMaximum === true) {
352 3
                if ($data >= $this->maximum) {
353 2
                    $this->fail(new NumericException(
354 2
                        'Value less or equal than ' . $this->maximum . ' expected, ' . $data . ' received',
355 3
                        NumericException::MAXIMUM), $path);
356
                }
357
            } else {
358 40
                if ($data > $this->maximum) {
359 13
                    $this->fail(new NumericException(
360 13
                        'Value less than ' . $this->minimum . ' expected, ' . $data . ' received',
361 13
                        NumericException::MAXIMUM), $path);
362
                }
363
            }
364
        }
365
366 1047
        if ($this->minimum !== null) {
367 516
            if ($this->exclusiveMinimum === true) {
368 93
                if ($data <= $this->minimum) {
369 2
                    $this->fail(new NumericException(
370 2
                        'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
371 93
                        NumericException::MINIMUM), $path);
372
                }
373
            } else {
374 441
                if ($data < $this->minimum) {
375 43
                    $this->fail(new NumericException(
376 43
                        'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
377 43
                        NumericException::MINIMUM), $path);
378
                }
379
            }
380
        }
381 1046
    }
382
383
    /**
384
     * @param mixed $data
385
     * @param Context $options
386
     * @param string $path
387
     * @return array|mixed|null|object|\stdClass
388
     * @throws InvalidValue
389
     * @throws \Exception
390
     * @throws \Swaggest\JsonDiff\Exception
391
     */
392 105
    private function processOneOf($data, Context $options, $path)
393
    {
394 105
        $successes = 0;
395 105
        $failures = '';
396 105
        $subErrors = [];
397 105
        $skipValidation = false;
398 105
        if ($options->skipValidation) {
399 41
            $skipValidation = true;
400 41
            $options->skipValidation = false;
401
        }
402
403 105
        $result = $data;
404 105
        foreach ($this->oneOf as $index => $item) {
405
            try {
406 105
                $result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf[' . $index . ']');
407 83
                $successes++;
408 83
                if ($successes > 1 || $options->skipValidation) {
409 83
                    break;
410
                }
411 73
            } catch (InvalidValue $exception) {
412 73
                $subErrors[$index] = $exception;
413 105
                $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
414
                // Expected exception
415
            }
416
        }
417 105
        if ($skipValidation) {
418 41
            $options->skipValidation = true;
419 41
            if ($successes === 0) {
420 8
                $result = self::unboolSchema($this->oneOf[0])->process($data, $options, $path . '->oneOf[0]');
421
            }
422
        }
423
424 105
        if (!$options->skipValidation) {
425 64
            if ($successes === 0) {
426 19
                $exception = new LogicException('No valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}");
427 19
                $exception->error = 'No valid results for oneOf';
428 19
                $exception->subErrors = $subErrors;
429 19
                $this->fail($exception, $path);
430 50
            } elseif ($successes > 1) {
431 17
                $exception = new LogicException('More than 1 valid result for oneOf: '
432 17
                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf {'
433 17
                    . "\n" . substr($failures, 0, -1) . "\n}");
434 17
                $exception->error = 'More than 1 valid result for oneOf';
435 17
                $exception->subErrors = $subErrors;
436 17
                $this->fail($exception, $path);
437
            }
438
        }
439 75
        return $result;
440
    }
441
442
    /**
443
     * @param mixed $data
444
     * @param Context $options
445
     * @param string $path
446
     * @return array|mixed|null|object|\stdClass
447
     * @throws InvalidValue
448
     * @throws \Exception
449
     * @throws \Swaggest\JsonDiff\Exception
450
     */
451 1714
    private function processAnyOf($data, Context $options, $path)
452
    {
453 1714
        $successes = 0;
454 1714
        $failures = '';
455 1714
        $subErrors = [];
456 1714
        $result = $data;
457 1714
        foreach ($this->anyOf as $index => $item) {
458
            try {
459 1714
                $result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf[' . $index . ']');
460 1708
                $successes++;
461 1708
                if ($successes) {
462 1708
                    break;
463
                }
464 430
            } catch (InvalidValue $exception) {
465 430
                $subErrors[$index] = $exception;
466 430
                $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
467
                // Expected exception
468
            }
469
        }
470 1714
        if (!$successes && !$options->skipValidation) {
471 29
            $exception = new LogicException('No valid results for anyOf {' . "\n"
472 29
                . substr(Helper::padLines(' ', $failures, false), 0, -1)
473 29
                . "\n}");
474 29
            $exception->error = 'No valid results for anyOf';
475 29
            $exception->subErrors = $subErrors;
476 29
            $this->fail($exception, $path);
477
        }
478 1708
        return $result;
479
    }
480
481
    /**
482
     * @param mixed $data
483
     * @param Context $options
484
     * @param string $path
485
     * @return array|mixed|null|object|\stdClass
486
     * @throws InvalidValue
487
     * @throws \Exception
488
     * @throws \Swaggest\JsonDiff\Exception
489
     */
490 353
    private function processAllOf($data, Context $options, $path)
491
    {
492 353
        $result = $data;
493 353
        foreach ($this->allOf as $index => $item) {
494 353
            $result = self::unboolSchema($item)->process($data, $options, $path . '->allOf[' . $index . ']');
495
        }
496 315
        return $result;
497
    }
498
499
    /**
500
     * @param mixed $data
501
     * @param Context $options
502
     * @param string $path
503
     * @return array|mixed|null|object|\stdClass
504
     * @throws InvalidValue
505
     * @throws \Exception
506
     * @throws \Swaggest\JsonDiff\Exception
507
     */
508 26
    private function processIf($data, Context $options, $path)
509
    {
510 26
        $valid = true;
511
        try {
512 26
            self::unboolSchema($this->if)->process($data, $options, $path . '->if');
513 13
        } catch (InvalidValue $exception) {
514 13
            $valid = false;
515
        }
516 26
        if ($valid) {
517 18
            if ($this->then !== null) {
518 18
                return self::unboolSchema($this->then)->process($data, $options, $path . '->then');
519
            }
520
        } else {
521 13
            if ($this->else !== null) {
522 6
                return self::unboolSchema($this->else)->process($data, $options, $path . '->else');
523
            }
524
        }
525 10
        return null;
526
    }
527
528
    /**
529
     * @param \stdClass $data
530
     * @param Context $options
531
     * @param string $path
532
     * @throws InvalidValue
533
     */
534 161
    private function processObjectRequired($data, Context $options, $path)
535
    {
536 161
        if (isset($this->__dataToProperty[$options->mapping])) {
537 2
            if ($options->import) {
538 1
                foreach ($this->required as $item) {
539 1
                    if (isset($this->__propertyToData[$options->mapping][$item])) {
540 1
                        $item = $this->__propertyToData[$options->mapping][$item];
541
                    }
542 1
                    if (!property_exists($data, $item)) {
543 1
                        $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($data, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
544
                    }
545
                }
546
            } else {
547 2
                foreach ($this->required as $item) {
548 2
                    if (isset($this->__dataToProperty[$options->mapping][$item])) {
549
                        $item = $this->__dataToProperty[$options->mapping][$item];
550
                    }
551 2
                    if (!property_exists($data, $item)) {
552 2
                        $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($data, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
553
                    }
554
                }
555
            }
556
557
        } else {
558 160
            foreach ($this->required as $item) {
559 156
                if (!property_exists($data, $item)) {
560 156
                    $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($data, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
561
                }
562
            }
563
        }
564 129
    }
565
566
    /**
567
     * @param \stdClass $data
568
     * @param Context $options
569
     * @param string $path
570
     * @param ObjectItemContract|null $result
571
     * @return array|null|ClassStructure|ObjectItemContract
572
     * @throws InvalidValue
573
     * @throws \Exception
574
     * @throws \Swaggest\JsonDiff\Exception
575
     */
576 3151
    private function processObject($data, Context $options, $path, $result = null)
577
    {
578 3151
        $import = $options->import;
579
580 3151
        if (!$options->skipValidation && $this->required !== null) {
581 161
            $this->processObjectRequired($data, $options, $path);
582
        }
583
584 3150
        if ($import) {
585 3137
            if (!$options->validateOnly) {
586
587 3137
                if ($this->useObjectAsArray) {
588 1
                    $result = array();
589 3136
                } elseif (!$result instanceof ObjectItemContract) {
590
                    //* todo check performance impact
591 3136
                    if (null === $this->objectItemClass) {
592 1156
                        $result = new ObjectItem();
593
                    } else {
594 3125
                        $className = $this->objectItemClass;
595 3125
                        if ($options->objectItemClassMapping !== null) {
596
                            if (isset($options->objectItemClassMapping[$className])) {
597
                                $className = $options->objectItemClassMapping[$className];
598
                            }
599
                        }
600 3125
                        $result = new $className;
601
                    }
602
                    //*/
603
604
605 3136
                    if ($result instanceof ClassStructure) {
606 3124
                        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...
607 3124
                            $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...
608
                            /** @noinspection PhpUnusedLocalVariableInspection */
609
                            /* 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...
610
                            $validateOnSetHandler = new ScopeExit(function () use ($result) {
611
                                $result->__validateOnSet = true;
612
                            });
613
                            //*/
614
                        }
615
                    }
616
617
                    //* todo check performance impact
618 3136
                    if ($result instanceof ObjectItemContract) {
619 3136
                        $result->setDocumentPath($path);
620
                    }
621
                    //*/
622
                }
623
            }
624
        }
625
626
        // @todo better check for schema id
627
628 3150
        if ($import
629 3150
            && isset($data->{Schema::PROP_ID_D4})
630 3150
            && ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO)
631 3150
            && is_string($data->{Schema::PROP_ID_D4})) {
632 35
            $id = $data->{Schema::PROP_ID_D4};
633 35
            $refResolver = $options->refResolver;
634 35
            $parentScope = $refResolver->updateResolutionScope($id);
635
            /** @noinspection PhpUnusedLocalVariableInspection */
636
            $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...
637 35
                $refResolver->setResolutionScope($parentScope);
638 35
            });
639
        }
640
641 3150
        if ($import
642 3150
            && isset($data->{self::PROP_ID})
643 3150
            && ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO)
644 3150
            && is_string($data->{self::PROP_ID})) {
645 114
            $id = $data->{self::PROP_ID};
646 114
            $refResolver = $options->refResolver;
647 114
            $parentScope = $refResolver->updateResolutionScope($id);
648
            /** @noinspection PhpUnusedLocalVariableInspection */
649
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
650 114
                $refResolver->setResolutionScope($parentScope);
651 114
            });
652
        }
653
654 3150
        if ($import) {
655
            try {
656
657 3137
                $refProperty = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $refProperty is dead and can be removed.
Loading history...
658 3137
                $dereference = true;
659
660 3137
                if (isset($data->{self::PROP_REF})) {
661 430
                    if (null === $refProperty = $this->properties[self::PROP_REF]) {
662 430
                        if (isset($this->__dataToProperty[$options->mapping][self::PROP_REF])) {
663 418
                            $refProperty = $this->properties[$this->__dataToProperty[$options->mapping][self::PROP_REF]];
664
                        }
665
                    }
666
667 430
                    if (isset($refProperty) && ($refProperty->format !== Format::URI_REFERENCE)) {
668 14
                        $dereference = false;
669
                    }
670
                }
671
672
                while (
673 3137
                    isset($data->{self::PROP_REF})
674 3137
                    && is_string($data->{self::PROP_REF})
675 3137
                    && $dereference
676
                ) {
677 418
                    $refString = $data->{self::PROP_REF};
678
679
                    // todo check performance impact
680 418
                    if ($refString === 'http://json-schema.org/draft-04/schema#'
681 408
                        || $refString === 'http://json-schema.org/draft-06/schema#'
682 418
                        || $refString === 'http://json-schema.org/draft-07/schema#') {
683 26
                        return Schema::schema();
684
                    }
685
686
                    // TODO consider process # by reference here ?
687 392
                    $refResolver = $options->refResolver;
688 392
                    $preRefScope = $refResolver->getResolutionScope();
689
                    /** @noinspection PhpUnusedLocalVariableInspection */
690
                    $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...
691 392
                        $refResolver->setResolutionScope($preRefScope);
692 392
                    });
693
694 392
                    $ref = $refResolver->resolveReference($refString);
695 392
                    $data = self::unboolSchemaData($ref->getData());
696 392
                    if (!$options->validateOnly) {
697 392
                        if ($ref->isImported()) {
698 174
                            $refResult = $ref->getImported();
699 174
                            return $refResult;
700
                        }
701 392
                        $ref->setImported($result);
702
                        try {
703 392
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString, $result);
704 392
                            if ($refResult instanceof ObjectItemContract) {
705 392
                                $refResult->setFromRef($refString);
706
                            }
707 392
                            $ref->setImported($refResult);
708 1
                        } catch (InvalidValue $exception) {
709 1
                            $ref->unsetImported();
710 1
                            throw $exception;
711
                        }
712 392
                        return $refResult;
713
                    } else {
714
                        $this->process($data, $options, $path . '->$ref:' . $refString);
715
                    }
716
                }
717 1
            } catch (InvalidValue $exception) {
718 1
                $this->fail($exception, $path);
719
            }
720
        }
721
722
        /** @var Schema[]|null $properties */
723 3150
        $properties = null;
724
725 3150
        $nestedProperties = null;
726 3150
        if ($this->properties !== null) {
727 3136
            $properties = $this->properties->toArray(); // todo call directly
728 3136
            if ($this->properties instanceof Properties) {
729 3136
                $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...
730
            } else {
731 579
                $nestedProperties = array();
732
            }
733
        }
734
735 3150
        $array = array();
736 3150
        if (!empty($this->__dataToProperty[$options->mapping])) { // todo skip on $options->validateOnly
737 3118
            foreach ((array)$data as $key => $value) {
738 3114
                if ($import) {
739 3112
                    if (isset($this->__dataToProperty[$options->mapping][$key])) {
740 3112
                        $key = $this->__dataToProperty[$options->mapping][$key];
741
                    }
742
                } else {
743 22
                    if (isset($this->__propertyToData[$options->mapping][$key])) {
744 1
                        $key = $this->__propertyToData[$options->mapping][$key];
745
                    }
746
                }
747 3118
                $array[$key] = $value;
748
            }
749
        } else {
750 1234
            $array = (array)$data;
751
        }
752
753 3150
        if (!$options->skipValidation) {
754 3129
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
755 4
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
756
            }
757 3129
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
758 3
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
759
            }
760 3129
            if ($this->propertyNames !== null) {
761 17
                $propertyNames = self::unboolSchema($this->propertyNames);
762 17
                foreach ($array as $key => $tmp) {
763 10
                    $propertyNames->process($key, $options, $path . '->propertyNames:' . $key);
764
                }
765
            }
766
        }
767
768 3150
        $defaultApplied = array();
769 3150
        if ($import
770 3150
            && !$options->validateOnly
771 3150
            && $options->applyDefaults
772 3150
            && $properties !== null
773
        ) {
774 38
            foreach ($properties as $key => $property) {
775
                // todo check when property is \stdClass `{}` here (RefTest)
776 36
                if ($property instanceof SchemaContract && null !== $default = $property->getDefault()) {
777 6
                    if (isset($this->__dataToProperty[$options->mapping][$key])) {
778
                        $key = $this->__dataToProperty[$options->mapping][$key];
779
                    }
780 6
                    if (!array_key_exists($key, $array)) {
781 6
                        $defaultApplied[$key] = true;
782 36
                        $array[$key] = $default;
783
                    }
784
                }
785
            }
786
        }
787
788 3150
        foreach ($array as $key => $value) {
789 3140
            if ($key === '' && PHP_VERSION_ID < 71000) {
790 1
                $this->fail(new InvalidValue('Empty property name'), $path);
791
            }
792
793 3139
            $found = false;
794
795 3139
            if (!$options->skipValidation && !empty($this->dependencies)) {
796 73
                $deps = $this->dependencies;
797 73
                if (isset($deps->$key)) {
798 63
                    $dependencies = $deps->$key;
799 63
                    $dependencies = self::unboolSchema($dependencies);
800 63
                    if ($dependencies instanceof SchemaContract) {
801 29
                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
802
                    } else {
803 34
                        foreach ($dependencies as $item) {
804 31
                            if (!property_exists($data, $item)) {
805 18
                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
806 31
                                    ObjectException::DEPENDENCY_MISSING), $path);
807
                            }
808
                        }
809
                    }
810
                }
811
            }
812
813 3139
            $propertyFound = false;
814 3139
            if (isset($properties[$key])) {
815
                /** @var Schema[] $properties */
816 3129
                $prop = self::unboolSchema($properties[$key]);
817 3129
                $propertyFound = true;
818 3129
                $found = true;
819 3129
                if ($prop instanceof SchemaContract) {
820 3072
                    $value = $prop->process(
821 3072
                        $value,
822 3072
                        isset($defaultApplied[$key]) ? $options->withDefault() : $options,
823 3072
                        $path . '->properties:' . $key
824
                    );
825
                }
826
            }
827
828
            /** @var Egg[] $nestedEggs */
829 3134
            $nestedEggs = null;
830 3134
            if (isset($nestedProperties[$key])) {
831 6
                $found = true;
832 6
                $nestedEggs = $nestedProperties[$key];
833
                // todo iterate all nested props?
834 6
                $value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key);
835
            }
836
837 3134
            if ($this->patternProperties !== null) {
838 166
                foreach ($this->patternProperties as $pattern => $propertySchema) {
839 166
                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
840 120
                        $found = true;
841 120
                        $value = self::unboolSchema($propertySchema)->process($value, $options,
842 120
                            $path . '->patternProperties[' . strtr($pattern, array('~' => '~1', ':' => '~2')) . ']:' . $key);
843 93
                        if (!$options->validateOnly && $import) {
844 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

844
                            $result->/** @scrutinizer ignore-call */ 
845
                                     addPatternPropertyName($pattern, $key);
Loading history...
845
                        }
846
                        //break; // todo manage multiple import data properly (pattern accessor)
847
                    }
848
                }
849
            }
850 3134
            if (!$found && $this->additionalProperties !== null) {
851 994
                if (!$options->skipValidation && $this->additionalProperties === false) {
852 12
                    $this->fail(new ObjectException('Additional properties not allowed: ' . $key), $path);
853
                }
854
855 994
                if ($this->additionalProperties instanceof SchemaContract) {
856 994
                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
857
                }
858
859 987
                if ($import && !$this->useObjectAsArray && !$options->validateOnly) {
860 985
                    $result->addAdditionalPropertyName($key);
861
                }
862
            }
863
864 3130
            if (!$options->validateOnly && $nestedEggs && $import) {
865 5
                foreach ($nestedEggs as $nestedEgg) {
866 5
                    $result->setNestedProperty($key, $value, $nestedEgg);
867
                }
868 5
                if ($propertyFound) {
869 5
                    $result->$key = $value;
870
                }
871
            } else {
872 3129
                if ($this->useObjectAsArray && $import) {
873 1
                    $result[$key] = $value;
874
                } else {
875 3129
                    if ($found || !$import) {
876 3128
                        $result->$key = $value;
877 1140
                    } elseif (!isset($result->$key)) {
878 3130
                        $result->$key = $value;
879
                    }
880
                }
881
            }
882
        }
883
884 3138
        return $result;
885
    }
886
887
    /**
888
     * @param array $data
889
     * @param Context $options
890
     * @param string $path
891
     * @param array $result
892
     * @return mixed
893
     * @throws InvalidValue
894
     * @throws \Exception
895
     * @throws \Swaggest\JsonDiff\Exception
896
     */
897 1216
    private function processArray($data, Context $options, $path, $result)
898
    {
899 1216
        $count = count($data);
900 1216
        if (!$options->skipValidation) {
901 1016
            if ($this->minItems !== null && $count < $this->minItems) {
902 9
                $this->fail(new ArrayException("Not enough items in array"), $path);
903
            }
904
905 1010
            if ($this->maxItems !== null && $count > $this->maxItems) {
906 6
                $this->fail(new ArrayException("Too many items in array"), $path);
907
            }
908
        }
909
910 1204
        $pathItems = 'items';
911 1204
        $this->items = self::unboolSchema($this->items);
912 1204
        if ($this->items instanceof SchemaContract) {
0 ignored issues
show
introduced by
$this->items is never a sub-type of Swaggest\JsonSchema\SchemaContract.
Loading history...
913 851
            $items = array();
914 851
            $additionalItems = $this->items;
915 690
        } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
916 690
            $items = array();
917 690
            $additionalItems = true;
918
        } else { // listed items
919 104
            $items = $this->items;
920 104
            $additionalItems = $this->additionalItems;
921 104
            $pathItems = 'additionalItems';
922
        }
923
924
        /**
925
         * @var Schema|Schema[] $items
926
         * @var null|bool|Schema $additionalItems
927
         */
928 1204
        $itemsLen = is_array($items) ? count($items) : 0;
929 1204
        $index = 0;
930 1204
        foreach ($result as $key => $value) {
931 1079
            if ($index < $itemsLen) {
932 94
                $itemSchema = self::unboolSchema($items[$index]);
933 94
                $result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
934
            } else {
935 1079
                if ($additionalItems instanceof SchemaContract) {
936 825
                    $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
937 825
                        . '[' . $index . ']:' . $index);
938 574
                } elseif (!$options->skipValidation && $additionalItems === false) {
939 6
                    $this->fail(new ArrayException('Unexpected array item'), $path);
940
                }
941
            }
942 1059
            ++$index;
943
        }
944
945 1180
        if (!$options->skipValidation && $this->uniqueItems) {
946 508
            if (!UniqueItems::isValid($data)) {
947 19
                $this->fail(new ArrayException('Array is not unique'), $path);
948
            }
949
        }
950
951 1161
        if (!$options->skipValidation && $this->contains !== null) {
952
            /** @var Schema|bool $contains */
953 36
            $contains = $this->contains;
954 36
            if ($contains === false) {
955 4
                $this->fail(new ArrayException('Contains is false'), $path);
956
            }
957 32
            if ($count === 0) {
958 7
                $this->fail(new ArrayException('Empty array fails contains constraint'), $path);
959
            }
960 25
            if ($contains === true) {
961 2
                $contains = self::unboolSchema($contains);
962
            }
963 25
            $containsOk = false;
964 25
            foreach ($data as $key => $item) {
965
                try {
966 25
                    $contains->process($item, $options, $path . '->' . $key);
967 17
                    $containsOk = true;
968 17
                    break;
969 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...
970
                }
971
            }
972 25
            if (!$containsOk) {
973 8
                $this->fail(new ArrayException('Array fails contains constraint'), $path);
974
            }
975
        }
976 1142
        return $result;
977
    }
978
979
    /**
980
     * @param mixed|string $data
981
     * @param Context $options
982
     * @param string $path
983
     * @return bool|mixed|string
984
     * @throws InvalidValue
985
     */
986 14
    private function processContent($data, Context $options, $path)
987
    {
988
        try {
989 14
            if ($options->unpackContentMediaType) {
990 7
                return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import);
991
            } else {
992 7
                Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true);
993
            }
994 4
        } catch (InvalidValue $exception) {
995 4
            $this->fail($exception, $path);
996
        }
997 7
        return $data;
998
    }
999
1000
    /**
1001
     * @param mixed $data
1002
     * @param Context $options
1003
     * @param string $path
1004
     * @param mixed|null $result
1005
     * @return array|mixed|null|object|\stdClass
1006
     * @throws InvalidValue
1007
     * @throws \Exception
1008
     * @throws \Swaggest\JsonDiff\Exception
1009
     */
1010 3197
    public function process($data, Context $options, $path = '#', $result = null)
1011
    {
1012 3197
        $import = $options->import;
1013
1014 3197
        if (!$import && $data instanceof SchemaExporter) {
1015 1
            $data = $data->exportSchema(); // Used to export ClassStructure::schema()
1016
        }
1017
1018 3197
        if (!$import && $data instanceof ObjectItemContract) {
1019 786
            $result = new \stdClass();
1020
1021 786
            if ('#' === $path) {
1022 773
                $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...
1023 773
                    foreach ($options->exportedDefinitions as $ref => $data) {
1024 4
                        JsonPointer::add($result, JsonPointer::splitPath($ref), $data,
1025 4
                            JsonPointer::SKIP_IF_ISSET + JsonPointer::RECURSIVE_KEY_CREATION);
1026
                    }
1027 773
                });
1028
            }
1029
1030 786
            if ('#' !== $path && $ref = $data->getFromRef()) {
1031 4
                if ($ref[0] === '#') {
1032 4
                    if (isset($options->exportedDefinitions[$ref])) {
1033 1
                        $result->{self::PROP_REF} = $ref;
1034 1
                        return $result;
1035 4
                    } elseif (!array_key_exists($ref, $options->exportedDefinitions)) {
1036 4
                        $exported = null;
1037 4
                        $options->exportedDefinitions[$ref] = &$exported;
1038 4
                        $exported = $this->process($data, $options, $ref);
0 ignored issues
show
Unused Code introduced by
The assignment to $exported is dead and can be removed.
Loading history...
1039 4
                        $result->{self::PROP_REF} = $ref;
1040 4
                        return $result;
1041
                    }
1042
                }
1043
            }
1044
1045 786
            if ($options->circularReferences->contains($data)) {
1046
                /** @noinspection PhpIllegalArrayKeyTypeInspection */
1047 1
                $path = $options->circularReferences[$data];
1048 1
                $result->{self::PROP_REF} = PointerUtil::getDataPointer($path, true);
1049 1
                return $result;
1050
            }
1051 786
            $options->circularReferences->attach($data, $path);
1052
1053 786
            $data = $data->jsonSerialize();
1054
        }
1055
1056 3197
        if ($ref = $this->getFromRef()) {
1057 376
            $path .= '->$ref[' . strtr($ref, array('~' => '~1', ':' => '~2')) . ']';
1058
        }
1059
1060 3197
        if (!$import && is_array($data) && $this->useObjectAsArray) {
1061 1
            $data = (object)$data;
1062
        }
1063
1064 3197
        if (null !== $options->dataPreProcessor) {
1065
            $data = $options->dataPreProcessor->process($data, $this, $import);
1066
        }
1067
1068 3197
        if ($result === null) {
1069 3197
            $result = $data;
1070
        }
1071
1072 3197
        if ($options->skipValidation) {
1073 1416
            goto skipValidation;
1074
        }
1075
1076 3159
        if ($this->type !== null) {
1077 3138
            $this->processType($data, $options, $path);
1078
        }
1079
1080 3158
        if ($this->enum !== null) {
1081 1476
            $this->processEnum($data, $path);
1082
        }
1083
1084 3158
        if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) {
1085 43
            $this->processConst($data, $path);
1086
        }
1087
1088 3158
        if ($this->not !== null) {
1089 69
            $this->processNot($data, $options, $path);
1090
        }
1091
1092 3158
        if (is_string($data)) {
1093 2239
            $this->processString($data, $path);
1094
        }
1095
1096 3158
        if (is_int($data) || is_float($data)) {
1097 1047
            $this->processNumeric($data, $path);
1098
        }
1099
1100 3158
        if ($this->if !== null) {
1101 26
            $result = $this->processIf($data, $options, $path);
1102
        }
1103
1104
        skipValidation:
1105
1106 3196
        if ($this->oneOf !== null) {
1107 105
            $result = $this->processOneOf($data, $options, $path);
1108
        }
1109
1110 3196
        if ($this->anyOf !== null) {
1111 1714
            $result = $this->processAnyOf($data, $options, $path);
1112
        }
1113
1114 3196
        if ($this->allOf !== null) {
1115 353
            $result = $this->processAllOf($data, $options, $path);
1116
        }
1117
1118 3196
        if ($data instanceof \stdClass) {
1119 3151
            $result = $this->processObject($data, $options, $path, $result);
1120
        }
1121
1122 3193
        if (is_array($data)) {
1123 1216
            $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

1123
            $result = $this->processArray($data, $options, $path, /** @scrutinizer ignore-type */ $result);
Loading history...
1124
        }
1125
1126 3193
        if ($this->contentEncoding !== null || $this->contentMediaType !== null) {
1127 14
            $result = $this->processContent($data, $options, $path);
1128
        }
1129
1130 3193
        return $result;
1131
    }
1132
1133
    /**
1134
     * @param boolean $useObjectAsArray
1135
     * @return Schema
1136
     */
1137 1
    public function setUseObjectAsArray($useObjectAsArray)
1138
    {
1139 1
        $this->useObjectAsArray = $useObjectAsArray;
1140 1
        return $this;
1141
    }
1142
1143
    /**
1144
     * @param InvalidValue $exception
1145
     * @param string $path
1146
     * @throws InvalidValue
1147
     */
1148 1222
    private function fail(InvalidValue $exception, $path)
1149
    {
1150 1222
        if ($path !== '#') {
1151 755
            $exception->addPath($path);
1152
        }
1153 1222
        throw $exception;
1154
    }
1155
1156 13
    public static function integer()
1157
    {
1158 13
        $schema = new static();
1159 13
        $schema->type = Type::INTEGER;
1160 13
        return $schema;
1161
    }
1162
1163 4
    public static function number()
1164
    {
1165 4
        $schema = new static();
1166 4
        $schema->type = Type::NUMBER;
1167 4
        return $schema;
1168
    }
1169
1170 9
    public static function string()
1171
    {
1172 9
        $schema = new static();
1173 9
        $schema->type = Type::STRING;
1174 9
        return $schema;
1175
    }
1176
1177 4
    public static function boolean()
1178
    {
1179 4
        $schema = new static();
1180 4
        $schema->type = Type::BOOLEAN;
1181 4
        return $schema;
1182
    }
1183
1184 6
    public static function object()
1185
    {
1186 6
        $schema = new static();
1187 6
        $schema->type = Type::OBJECT;
1188 6
        return $schema;
1189
    }
1190
1191 1
    public static function arr()
1192
    {
1193 1
        $schema = new static();
1194 1
        $schema->type = Type::ARR;
1195 1
        return $schema;
1196
    }
1197
1198
    public static function null()
1199
    {
1200
        $schema = new static();
1201
        $schema->type = Type::NULL;
1202
        return $schema;
1203
    }
1204
1205
1206
    /**
1207
     * @param Properties $properties
1208
     * @return Schema
1209
     */
1210 3
    public function setProperties($properties)
1211
    {
1212 3
        $this->properties = $properties;
1213 3
        return $this;
1214
    }
1215
1216
    /**
1217
     * @param string $name
1218
     * @param Schema $schema
1219
     * @return $this
1220
     */
1221 3
    public function setProperty($name, $schema)
1222
    {
1223 3
        if (null === $this->properties) {
1224 3
            $this->properties = new Properties();
1225
        }
1226 3
        $this->properties->__set($name, $schema);
1227 3
        return $this;
1228
    }
1229
1230
    /**
1231
     * @param string $name
1232
     * @param SchemaContract $schema
1233
     * @return $this
1234
     * @throws Exception
1235
     */
1236
    public function setPatternProperty($name, $schema)
1237
    {
1238
        if (null === $this->patternProperties) {
1239
            $this->patternProperties = new Properties();
1240
        }
1241
        $this->patternProperties->__set($name, $schema);
1242
        return $this;
1243
    }
1244
1245
1246
    /** @var mixed[] */
1247
    private $metaItems = array();
1248
1249 1
    public function addMeta($meta, $name = null)
1250
    {
1251 1
        if ($name === null) {
1252 1
            $name = get_class($meta);
1253
        }
1254 1
        $this->metaItems[$name] = $meta;
1255 1
        return $this;
1256
    }
1257
1258 1
    public function getMeta($name)
1259
    {
1260 1
        if (isset($this->metaItems[$name])) {
1261 1
            return $this->metaItems[$name];
1262
        }
1263
        return null;
1264
    }
1265
1266
    /**
1267
     * @param Context $options
1268
     * @return ObjectItemContract
1269
     */
1270 5
    public function makeObjectItem(Context $options = null)
1271
    {
1272 5
        if (null === $this->objectItemClass) {
1273
            return new ObjectItem();
1274
        } else {
1275 5
            $className = $this->objectItemClass;
1276 5
            if ($options !== null) {
1277
                if (isset($options->objectItemClassMapping[$className])) {
1278
                    $className = $options->objectItemClassMapping[$className];
1279
                }
1280
            }
1281 5
            return new $className;
1282
        }
1283
    }
1284
1285
    /**
1286
     * @param mixed $schema
1287
     * @return mixed|Schema
1288
     */
1289 3209
    private static function unboolSchema($schema)
1290
    {
1291 3209
        static $trueSchema;
1292 3209
        static $falseSchema;
1293
1294 3209
        if (null === $trueSchema) {
1295 1
            $trueSchema = new Schema();
1296 1
            $trueSchema->__booleanSchema = true;
1297 1
            $falseSchema = new Schema();
1298 1
            $falseSchema->not = $trueSchema;
1299 1
            $falseSchema->__booleanSchema = false;
1300
        }
1301
1302 3209
        if ($schema === true) {
1303 108
            return $trueSchema;
1304 3181
        } elseif ($schema === false) {
1305 94
            return $falseSchema;
1306
        } else {
1307 3149
            return $schema;
1308
        }
1309
    }
1310
1311
    /**
1312
     * @param mixed $data
1313
     * @return \stdClass
1314
     */
1315 392
    private static function unboolSchemaData($data)
1316
    {
1317 392
        static $trueSchema;
1318 392
        static $falseSchema;
1319
1320 392
        if (null === $trueSchema) {
1321 1
            $trueSchema = new \stdClass();
1322 1
            $falseSchema = new \stdClass();
1323 1
            $falseSchema->not = $trueSchema;
1324
        }
1325
1326 392
        if ($data === true) {
1327 6
            return $trueSchema;
1328 388
        } elseif ($data === false) {
1329 5
            return $falseSchema;
1330
        } else {
1331 383
            return $data;
1332
        }
1333
    }
1334
1335 36
    public function getDefault()
1336
    {
1337 36
        return $this->default;
1338
    }
1339
1340 91
    public function getProperties()
1341
    {
1342 91
        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...
1343
    }
1344
1345
    public function getObjectItemClass()
1346
    {
1347
        return $this->objectItemClass;
1348
    }
1349
1350
    /**
1351
     * @return string[]
1352
     */
1353
    public function getPropertyNames()
1354
    {
1355
        return array_keys($this->getProperties()->toArray());
1356
    }
1357
1358
    /**
1359
     * @return string[]
1360
     */
1361
    public function getNestedPropertyNames()
1362
    {
1363
        return $this->getProperties()->nestedPropertyNames;
1364
    }
1365
1366
}
1367