Passed
Pull Request — master (#61)
by
unknown
03:16
created

Schema::makeObjectItem()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.8437

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 12
c 0
b 0
f 0
ccs 5
cts 8
cp 0.625
rs 10
cc 4
nc 4
nop 1
crap 4.8437
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;
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 3295
    public static function import($data, Context $options = null)
110
    {
111 3295
        if (null === $options) {
112 31
            $options = new Context();
113
        }
114
115 3295
        $options->applyDefaults = false;
116
117 3295
        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 3295
        if (is_string($data)) {
129 4
            $data = (object)array(self::PROP_REF => $data);
130
        }
131
132 3295
        $data = self::unboolSchema($data);
133 3295
        if ($data instanceof SchemaContract) {
134 72
            return $data;
135
        }
136
137 3223
        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 3317
    public function in($data, Context $options = null)
149
    {
150 3317
        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 3263
        if ($options === null) {
159 65
            $options = new Context();
160
        }
161
162 3263
        $options->import = true;
163
164 3263
        if ($options->refResolver === null) {
165 2930
            $options->refResolver = new RefResolver($data);
166
        } else {
167 1766
            $options->refResolver->setRootData($data);
168
        }
169
170 3263
        if ($options->remoteRefProvider) {
171 3190
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
172
        }
173
174 3263
        $options->refResolver->preProcessReferences($data, $options);
175
176 3263
        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 2448
    public function out($data, Context $options = null)
188
    {
189 2448
        if ($options === null) {
190 1005
            $options = new Context();
191
        }
192
193 2448
        $options->circularReferences = new \SplObjectStorage();
194 2448
        $options->import = false;
195 2448
        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 3250
    private function processType($data, Context $options, $path = '#')
206
    {
207 3250
        if ($options->tolerateStrings && is_string($data)) {
208
            $valid = Type::readString($this->type, $data);
209
        } else {
210 3250
            $valid = Type::isValid($this->type, $data, $options->version);
211
        }
212 3250
        if (!$valid) {
213 696
            $this->fail(new TypeException(ucfirst(
214 696
                    implode(', ', is_array($this->type) ? $this->type : array($this->type))
215 696
                    . ' expected, ' . json_encode($data) . ' received')
216 696
            ), $path);
217
        }
218 3248
    }
219
220
    /**
221
     * @param mixed $data
222
     * @param string $path
223
     * @throws InvalidValue
224
     * @throws \Exception
225
     */
226 1510
    private function processEnum($data, $path = '#')
227
    {
228 1510
        $enumOk = false;
229 1510
        foreach ($this->enum as $item) {
230 1510
            if ($item === $data) {
231 1490
                $enumOk = true;
232 1490
                break;
233
            } else {
234 1446
                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 1446
                        break;
239
                    }
240
                }
241
            }
242
        }
243 1510
        if (!$enumOk) {
244 107
            $this->fail(new EnumException('Enum failed, enum: ' . json_encode($this->enum) . ', data: ' . json_encode($data)), $path);
245
        }
246 1493
    }
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 70
    private function processNot($data, Context $options, $path)
279
    {
280 70
        $exception = false;
281
        try {
282 70
            self::unboolSchema($this->not)->process($data, $options, $path . '->not');
283 16
        } catch (InvalidValue $exception) {
284
            // Expected exception
285
        }
286 70
        if ($exception === false) {
287 56
            $this->fail(new LogicException('Not ' . json_encode($this->not) . ' expected, ' . json_encode($data) . ' received'), $path . '->not');
288
        }
289 16
    }
290
291
    /**
292
     * @param string $data
293
     * @param string $path
294
     * @throws InvalidValue
295
     */
296 2325
    private function processString($data, $path)
297
    {
298 2325
        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 2319
        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 2316
        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 2316
        if ($this->format !== null) {
315 439
            $validationError = Format::validationError($this->format, $data);
316 439
            if ($validationError !== null) {
317 150
                if (!($this->format === "uri" && substr($path, -3) === ':id')) {
318 131
                    $this->fail(new StringException($validationError), $path);
319
                }
320
            }
321
        }
322 2316
    }
323
324
    /**
325
     * @param float|int $data
326
     * @param string $path
327
     * @throws InvalidValue
328
     */
329 1084
    private function processNumeric($data, $path)
330
    {
331 1084
        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 1084
        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 1084
        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 1084
        if ($this->maximum !== null) {
355 46
            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 43
                if ($data > $this->maximum) {
363 14
                    $this->fail(new NumericException(
364 14
                        'Value less than ' . $this->maximum . ' expected, ' . $data . ' received',
365 14
                        NumericException::MAXIMUM), $path);
366
                }
367
            }
368
        }
369
370 1084
        if ($this->minimum !== null) {
371 523
            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 448
                if ($data < $this->minimum) {
379 46
                    $this->fail(new NumericException(
380 46
                        'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
381 46
                        NumericException::MINIMUM), $path);
382
                }
383
            }
384
        }
385 1083
    }
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 107
    private function processOneOf($data, Context $options, $path)
397
    {
398 107
        $successes = 0;
399 107
        $failures = '';
400 107
        $subErrors = [];
401 107
        $skipValidation = false;
402 107
        if ($options->skipValidation) {
403 41
            $skipValidation = true;
404 41
            $options->skipValidation = false;
405
        }
406
407 107
        $result = $data;
408 107
        foreach ($this->oneOf as $index => $item) {
409
            try {
410 107
                $result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf[' . $index . ']');
411 85
                $successes++;
412 85
                if ($successes > 1 || $options->skipValidation) {
413 85
                    break;
414
                }
415 75
            } catch (InvalidValue $exception) {
416 75
                $subErrors[$index] = $exception;
417 107
                $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
418
                // Expected exception
419
            }
420
        }
421 107
        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 107
        if (!$options->skipValidation) {
429 66
            if ($successes === 0) {
430 20
                $exception = new LogicException('No valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}");
431 20
                $exception->error = 'No valid results for oneOf';
432 20
                $exception->subErrors = $subErrors;
433 20
                $this->fail($exception, $path);
434 52
            } 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 77
        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 1760
    private function processAnyOf($data, Context $options, $path)
456
    {
457 1760
        $successes = 0;
458 1760
        $failures = '';
459 1760
        $subErrors = [];
460 1760
        $result = $data;
461 1760
        foreach ($this->anyOf as $index => $item) {
462
            try {
463 1760
                $result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf[' . $index . ']');
464 1754
                $successes++;
465 1754
                if ($successes) {
466 1754
                    break;
467
                }
468 445
            } catch (InvalidValue $exception) {
469 445
                $subErrors[$index] = $exception;
470 445
                $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
471
                // Expected exception
472
            }
473
        }
474 1760
        if (!$successes && !$options->skipValidation) {
475 31
            $exception = new LogicException('No valid results for anyOf {' . "\n"
476 31
                . substr(Helper::padLines(' ', $failures, false), 0, -1)
477 31
                . "\n}");
478 31
            $exception->error = 'No valid results for anyOf';
479 31
            $exception->subErrors = $subErrors;
480 31
            $this->fail($exception, $path);
481
        }
482 1754
        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 314
        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 object $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 object $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 3263
    private function processObject($data, Context $options, $path, $result = null)
581
    {
582 3263
        $import = $options->import;
583
584 3263
        if (!$options->skipValidation && $this->required !== null) {
585 161
            $this->processObjectRequired($data, $options, $path);
586
        }
587
588 3262
        if ($import) {
589 3245
            if (!$options->validateOnly) {
590
591 3245
                if ($this->useObjectAsArray) {
592 1
                    $result = array();
593 3244
                } elseif (!$result instanceof ObjectItemContract) {
594
                    //* todo check performance impact
595 3244
                    if (null === $this->objectItemClass) {
596 1183
                        $result = new ObjectItem();
597
                    } else {
598 3233
                        $className = $this->objectItemClass;
599 3233
                        if ($options->objectItemClassMapping !== null) {
600
                            if (isset($options->objectItemClassMapping[$className])) {
601
                                $className = $options->objectItemClassMapping[$className];
602
                            }
603
                        }
604 3233
                        $result = new $className;
605
                    }
606
                    //*/
607
608
609 3244
                    if ($result instanceof ClassStructure) {
610 3232
                        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 3232
                            $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 3244
                    if ($result instanceof ObjectItemContract) {
623 3244
                        $result->setDocumentPath($path);
624
                    }
625
                    //*/
626
                }
627
            }
628
        }
629
630
        // @todo better check for schema id
631
632 3262
        if ($import
633 3262
            && isset($data->{Schema::PROP_ID_D4})
634 3262
            && ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO)
635 3262
            && is_string($data->{Schema::PROP_ID_D4})) {
636 42
            $id = $data->{Schema::PROP_ID_D4};
637 42
            $refResolver = $options->refResolver;
638 42
            $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 42
                $refResolver->setResolutionScope($parentScope);
642 42
            });
643
        }
644
645 3262
        if ($import
646 3262
            && isset($data->{self::PROP_ID})
647 3262
            && ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO)
648 3262
            && 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 3262
        if ($import) {
659
            try {
660
661 3245
                $refProperty = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $refProperty is dead and can be removed.
Loading history...
662 3245
                $dereference = true;
663
664 3245
                if (isset($data->{self::PROP_REF})) {
665 447
                    if (null === $refProperty = $this->properties[self::PROP_REF]) {
666 447
                        if (isset($this->__dataToProperty[$options->mapping][self::PROP_REF])) {
667 435
                            $refProperty = $this->properties[$this->__dataToProperty[$options->mapping][self::PROP_REF]];
668
                        }
669
                    }
670
671 447
                    if (isset($refProperty) && ($refProperty->format !== Format::URI_REFERENCE)) {
672 14
                        $dereference = false;
673
                    }
674
                }
675
676
                if (
677 3245
                    isset($data->{self::PROP_REF})
678 3245
                    && is_string($data->{self::PROP_REF})
679 3245
                    && $dereference
680
                ) {
681 435
                    $refString = $data->{self::PROP_REF};
682
683
                    // todo check performance impact
684 435
                    if ($refString === 'http://json-schema.org/draft-04/schema#'
685 425
                        || $refString === 'http://json-schema.org/draft-06/schema#'
686 435
                        || $refString === 'http://json-schema.org/draft-07/schema#') {
687 26
                        return Schema::schema();
688
                    }
689
690
                    // TODO consider process # by reference here ?
691 409
                    $refResolver = $options->refResolver;
692 409
                    $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 409
                        $refResolver->setResolutionScope($preRefScope);
696 409
                    });
697
698 409
                    $ref = $refResolver->resolveReference($refString);
699 409
                    $data = self::unboolSchemaData($ref->getData());
700 409
                    if (!$options->validateOnly) {
701 409
                        if ($ref->isImported()) {
702 185
                            $refResult = $ref->getImported();
703 185
                            return $refResult;
704
                        }
705 409
                        $ref->setImported($result);
706
                        try {
707 409
                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString, $result);
708 409
                            if ($refResult instanceof ObjectItemContract) {
709 409
                                if ($refResult->getFromRefs()) {
710 47
                                    $refResult = clone $refResult; // @todo check performance, consider option
711
                                }
712 409
                                $refResult->setFromRef($refString);
713
                            }
714 409
                            $ref->setImported($refResult);
715 1
                        } catch (InvalidValue $exception) {
716 1
                            $ref->unsetImported();
717 1
                            throw $exception;
718
                        }
719 409
                        return $refResult;
720
                    } else {
721 3245
                        $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 3262
        $properties = null;
731
732 3262
        $nestedProperties = null;
733 3262
        if ($this->properties !== null) {
734 3248
            $properties = $this->properties->toArray(); // todo call directly
735 3248
            if ($this->properties instanceof Properties) {
736 3248
                $nestedProperties = $this->properties->nestedProperties;
737
            } else {
738 588
                $nestedProperties = array();
739
            }
740
        }
741
742 3262
        $array = array();
743 3262
        if (!empty($this->__dataToProperty[$options->mapping])) { // todo skip on $options->validateOnly
744 3228
            foreach (!$data instanceof \stdClass ? get_object_vars($data) : (array)$data as $key => $value) {
745 3224
                if ($import) {
746 3220
                    if (isset($this->__dataToProperty[$options->mapping][$key])) {
747 3220
                        $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 3228
                $array[$key] = $value;
755
            }
756
        } else {
757 1275
            $array = !$data instanceof \stdClass ? get_object_vars($data) : (array)$data;
758
        }
759
760 3262
        if (!$options->skipValidation) {
761 3241
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
762 4
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
763
            }
764 3241
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
765 3
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
766
            }
767 3241
            if ($this->propertyNames !== null) {
768 19
                $propertyNames = self::unboolSchema($this->propertyNames);
769 19
                foreach ($array as $key => $tmp) {
770 12
                    $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 3262
        $defaultApplied = array();
776 3262
        if ($import
777 3262
            && !$options->validateOnly
778 3262
            && $options->applyDefaults
779 3262
            && $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 3262
        foreach ($array as $key => $value) {
796 3252
            if ($key === '' && PHP_VERSION_ID < 71000) {
797 1
                $this->fail(new InvalidValue('Empty property name'), $path);
798
            }
799
800 3251
            $found = false;
801
802 3251
            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 3251
            $propertyFound = false;
821 3251
            if (isset($properties[$key])) {
822
                /** @var Schema[] $properties */
823 3241
                $prop = self::unboolSchema($properties[$key]);
824 3241
                $propertyFound = true;
825 3241
                $found = true;
826 3241
                if ($prop instanceof SchemaContract) {
827 3184
                    $value = $prop->process(
828 3184
                        $value,
829 3184
                        isset($defaultApplied[$key]) ? $options->withDefault() : $options,
830 3184
                        $path . '->properties:' . $key
831
                    );
832
                }
833
            }
834
835
            /** @var Egg[] $nestedEggs */
836 3246
            $nestedEggs = null;
837 3246
            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 3246
            if ($this->patternProperties !== null) {
845 178
                foreach ($this->patternProperties as $pattern => $propertySchema) {
846 178
                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
847 126
                        $found = true;
848 126
                        $value = self::unboolSchema($propertySchema)->process($value, $options,
849 126
                            $path . '->patternProperties[' . strtr($pattern, array('~' => '~1', ':' => '~2')) . ']:' . $key);
850 99
                        if (!$options->validateOnly && $import) {
851 156
                            $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 3246
            if (!$found && $this->additionalProperties !== null) {
858 1021
                if (!$options->skipValidation && $this->additionalProperties === false) {
859 15
                    $this->fail(new ObjectException('Additional properties not allowed: ' . $key), $path);
860
                }
861
862 1021
                if ($this->additionalProperties instanceof SchemaContract) {
863 1021
                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
864
                }
865
866 1014
                if ($import && !$this->useObjectAsArray && !$options->validateOnly) {
867 1010
                    $result->addAdditionalPropertyName($key);
868
                }
869
            }
870
871 3242
            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 3241
                if ($this->useObjectAsArray && $import) {
880 1
                    $result[$key] = $value;
881
                } else {
882 3241
                    if ($found || !$import) {
883 3240
                        $result->$key = $value;
884 1178
                    } elseif (!isset($result->$key)) {
885 3242
                        $result->$key = $value;
886
                    }
887
                }
888
            }
889
        }
890
891 3249
        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 1238
    private function processArray($data, Context $options, $path, $result)
905
    {
906 1238
        $count = count($data);
907 1238
        if (!$options->skipValidation) {
908 1038
            if ($this->minItems !== null && $count < $this->minItems) {
909 9
                $this->fail(new ArrayException("Not enough items in array"), $path);
910
            }
911
912 1032
            if ($this->maxItems !== null && $count > $this->maxItems) {
913 6
                $this->fail(new ArrayException("Too many items in array"), $path);
914
            }
915
        }
916
917 1226
        $pathItems = 'items';
918 1226
        $this->items = self::unboolSchema($this->items);
919 1226
        if ($this->items instanceof SchemaContract) {
0 ignored issues
show
introduced by
$this->items is never a sub-type of Swaggest\JsonSchema\SchemaContract.
Loading history...
920 873
            $items = array();
921 873
            $additionalItems = $this->items;
922 703
        } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
923 703
            $items = array();
924 703
            $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 1226
        $itemsLen = is_array($items) ? count($items) : 0;
936 1226
        $index = 0;
937 1226
        foreach ($result as $key => $value) {
938 1101
            if ($index < $itemsLen) {
939 94
                $itemSchema = self::unboolSchema($items[$index]);
940 94
                $result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
941
            } else {
942 1101
                if ($additionalItems instanceof SchemaContract) {
943 847
                    $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
944 847
                        . '[' . $index . ']:' . $index);
945 587
                } elseif (!$options->skipValidation && $additionalItems === false) {
946 6
                    $this->fail(new ArrayException('Unexpected array item'), $path);
947
                }
948
            }
949 1079
            ++$index;
950
        }
951
952 1200
        if (!$options->skipValidation && $this->uniqueItems) {
953 522
            if (!UniqueItems::isValid($data)) {
954 22
                $this->fail(new ArrayException('Array is not unique'), $path);
955
            }
956
        }
957
958 1181
        if (!$options->skipValidation && $this->contains !== null) {
959
            /** @var Schema|bool $contains */
960 36
            $contains = $this->contains;
961 36
            if ($contains === false) {
0 ignored issues
show
introduced by
The condition $contains === false is always false.
Loading history...
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) {
0 ignored issues
show
introduced by
The condition $contains === true is always false.
Loading history...
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 1162
        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 20
    private function processContent($data, Context $options, $path)
994
    {
995
        try {
996 20
            if ($options->unpackContentMediaType) {
997 10
                return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import);
998
            } else {
999 10
                Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true);
1000
            }
1001 4
        } catch (InvalidValue $exception) {
1002 4
            $this->fail($exception, $path);
1003
        }
1004 10
        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 3309
    public function process($data, Context $options, $path = '#', $result = null)
1018
    {
1019 3309
        $import = $options->import;
1020
1021 3309
        if (!$import && $data instanceof SchemaExporter) {
1022 2
            $data = $data->exportSchema(); // Used to export ClassStructure::schema()
1023
        }
1024
1025 3309
        if (!$import && $data instanceof ObjectItemContract) {
1026 805
            $result = new \stdClass();
1027
1028 805
            if ('#' === $path) {
1029 791
                $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 791
                    foreach ($options->exportedDefinitions as $ref => $data) {
1031 6
                        if ($data !== null) {
1032
                            // fix external reference
1033 6
                            $pathItems = explode('#', $ref,2);
1034 6
                            if ((count($pathItems) > 1) && (strlen($pathItems[0]) > 0)){
1035
                                if ($pathItems[1] == '/'){
1036
                                    $ref = "#";
1037
                                } else {
1038
                                    $ref = "#" . $pathItems[1];
1039
                                }
1040
                            }
1041 6
                            JsonPointer::add($result, JsonPointer::splitPath($ref), $data,
1042
                            /*JsonPointer::SKIP_IF_ISSET + */
1043 6
                            JsonPointer::RECURSIVE_KEY_CREATION);
1044
                        }
1045
                    }
1046 791
                });
1047
            }
1048
1049 805
            if ($options->isRef) {
1050 6
                $options->isRef = false;
1051
            } else {
1052 805
                if ('#' !== $path && $refs = $data->getFromRefs()) {
1053 6
                    $ref = $refs[0];
1054 6
                    if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1055 6
                        $exported = null;
1056 6
                        $options->exportedDefinitions[$ref] = &$exported;
1057 6
                        $options->isRef = true;
1058 6
                        $exported = $this->process($data, $options, $ref);
1059 6
                        unset($exported);
1060
                    }
1061
1062 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...
1063 2
                        $ref = $refs[$i];
1064 2
                        if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
1065 2
                            $exported = new \stdClass();
1066 2
                            $exported->{self::PROP_REF} = $refs[$i - 1];
1067 2
                            $options->exportedDefinitions[$ref] = $exported;
1068
                        }
1069
                    }
1070
1071 6
                    $result->{self::PROP_REF} = $refs[count($refs) - 1];
1072 6
                    return $result;
1073
                }
1074
            }
1075
1076 805
            if ($options->circularReferences->contains($data)) {
1077
                /** @noinspection PhpIllegalArrayKeyTypeInspection */
1078 1
                $path = $options->circularReferences[$data];
1079 1
                $result->{self::PROP_REF} = PointerUtil::getDataPointer($path, true);
1080 1
                return $result;
1081
            }
1082 805
            $options->circularReferences->attach($data, $path);
1083
1084 805
            $data = $data->jsonSerialize();
1085
        }
1086
1087 3309
        $path .= $this->getFromRefPath();
1088
1089 3309
        if (!$import && is_array($data) && $this->useObjectAsArray) {
1090 1
            $data = (object)$data;
1091
        }
1092
1093 3309
        if (null !== $options->dataPreProcessor) {
1094
            $data = $options->dataPreProcessor->process($data, $this, $import);
1095
        }
1096
1097 3309
        if ($result === null) {
1098 3309
            $result = $data;
1099
        }
1100
1101 3309
        if ($options->skipValidation) {
1102 1450
            goto skipValidation;
1103
        }
1104
1105 3271
        if ($this->type !== null) {
1106 3250
            $this->processType($data, $options, $path);
1107
        }
1108
1109 3270
        if ($this->enum !== null) {
1110 1510
            $this->processEnum($data, $path);
1111
        }
1112
1113 3270
        if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) {
1114 43
            $this->processConst($data, $path);
1115
        }
1116
1117 3270
        if ($this->not !== null) {
1118 70
            $this->processNot($data, $options, $path);
1119
        }
1120
1121 3270
        if (is_string($data)) {
1122 2325
            $this->processString($data, $path);
1123
        }
1124
1125 3270
        if (is_int($data) || is_float($data)) {
1126 1084
            $this->processNumeric($data, $path);
1127
        }
1128
1129 3270
        if ($this->if !== null) {
1130 26
            $result = $this->processIf($data, $options, $path);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $this->processIf($data, $options, $path) targeting Swaggest\JsonSchema\Schema::processIf() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1131
        }
1132
1133
        skipValidation:
1134
1135 3308
        if ($this->oneOf !== null) {
1136 107
            $result = $this->processOneOf($data, $options, $path);
1137
        }
1138
1139 3308
        if ($this->anyOf !== null) {
1140 1760
            $result = $this->processAnyOf($data, $options, $path);
1141
        }
1142
1143 3308
        if ($this->allOf !== null) {
1144 354
            $result = $this->processAllOf($data, $options, $path);
1145
        }
1146
1147 3308
        if (is_object($data)) {
1148 3263
            $result = $this->processObject($data, $options, $path, $result);
1149
        }
1150
1151 3305
        if (is_array($data)) {
1152 1238
            $result = $this->processArray($data, $options, $path, $result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type Swaggest\JsonSchema\Schema and Swaggest\JsonSchema\Structure\ClassStructure and Swaggest\JsonSchema\Structure\ObjectItemContract and object and stdClass; however, parameter $result of Swaggest\JsonSchema\Schema::processArray() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

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