Completed
Pull Request — master (#19)
by Viacheslav
08:06
created

Schema::processType()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.025

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 9
cts 10
cp 0.9
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 4
nop 3
crap 5.025
1
<?php
2
3
namespace Swaggest\JsonSchema;
4
5
6
use PhpLang\ScopeExit;
7
use Swaggest\JsonDiff\JsonDiff;
8
use Swaggest\JsonSchema\Constraint\Content;
9
use Swaggest\JsonSchema\Constraint\Format;
10
use Swaggest\JsonSchema\Constraint\Properties;
11
use Swaggest\JsonSchema\Constraint\Type;
12
use Swaggest\JsonSchema\Constraint\UniqueItems;
13
use Swaggest\JsonSchema\Exception\ArrayException;
14
use Swaggest\JsonSchema\Exception\ConstException;
15
use Swaggest\JsonSchema\Exception\EnumException;
16
use Swaggest\JsonSchema\Exception\LogicException;
17
use Swaggest\JsonSchema\Exception\NumericException;
18
use Swaggest\JsonSchema\Exception\ObjectException;
19
use Swaggest\JsonSchema\Exception\StringException;
20
use Swaggest\JsonSchema\Exception\TypeException;
21
use Swaggest\JsonSchema\Meta\Meta;
22
use Swaggest\JsonSchema\Meta\MetaHolder;
23
use Swaggest\JsonSchema\Structure\ClassStructure;
24
use Swaggest\JsonSchema\Structure\Egg;
25
use Swaggest\JsonSchema\Structure\ObjectItem;
26
use Swaggest\JsonSchema\Structure\ObjectItemContract;
27
28
/**
29
 * Class Schema
30
 * @package Swaggest\JsonSchema
31
 */
32
class Schema extends JsonSchema implements MetaHolder
33
{
34
    const CONST_PROPERTY = 'const';
35
36
    const DEFAULT_MAPPING = 'default';
37
38
    const VERSION_AUTO = 'a';
39
    const VERSION_DRAFT_04 = 4;
40
    const VERSION_DRAFT_06 = 6;
41
    const VERSION_DRAFT_07 = 7;
42
43
    const REF = '$ref';
44
    const ID = '$id';
45
    const ID_D4 = 'id';
46
47
    // Object
48
    /** @var Properties|Schema[]|Schema */
49
    public $properties;
50
    /** @var Schema|bool */
51
    public $additionalProperties;
52
    /** @var Schema[] */
53
    public $patternProperties;
54
    /** @var string[][]|Schema[]|\stdClass */
55
    public $dependencies;
56
57
    // Array
58
    /** @var null|Schema|Schema[] */
59
    public $items;
60
    /** @var null|Schema|bool */
61
    public $additionalItems;
62
63
    /** @var Schema[] */
64
    public $allOf;
65
    /** @var Schema */
66
    public $not;
67
    /** @var Schema[] */
68
    public $anyOf;
69
    /** @var Schema[] */
70
    public $oneOf;
71
72
    /** @var Schema */
73
    public $if;
74
    /** @var Schema */
75
    public $then;
76
    /** @var Schema */
77
    public $else;
78
79
80
    public $objectItemClass;
81
    private $useObjectAsArray = false;
82
83
    private $__dataToProperty = array();
84
    private $__propertyToData = array();
85
86
    private $__booleanSchema;
87
88 3
    public function addPropertyMapping($dataName, $propertyName, $mapping = self::DEFAULT_MAPPING)
89
    {
90 3
        $this->__dataToProperty[$mapping][$dataName] = $propertyName;
91 3
        $this->__propertyToData[$mapping][$propertyName] = $dataName;
92 3
        return $this;
93
    }
94
95
    /**
96
     * @param mixed $data
97
     * @param Context|null $options
98
     * @return static|JsonSchema
99
     * @throws Exception
100
     * @throws InvalidValue
101
     * @throws \Exception
102
     */
103 3171
    public static function import($data, Context $options = null)
104
    {
105 3171
        if (null === $options) {
106 21
            $options = new Context();
107
        }
108
109
        //$options->applyDefaults = false; // todo check infinite recursion on items, additionalProperties, etc...
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% 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...
110
111 3171
        if (isset($options->schemasCache) && is_object($data)) {
112
            if ($options->schemasCache->contains($data)) {
113
                return $options->schemasCache->offsetGet($data);
114
            } else {
115
                $schema = parent::import($data, $options);
116
                $options->schemasCache->attach($data, $schema);
117
                return $schema;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $schema; (Swaggest\JsonSchema\Schema) is incompatible with the return type of the parent method Swaggest\JsonSchema\Stru...\ClassStructure::import of type Swaggest\JsonSchema\Structure\ClassStructureTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
118
            }
119
        }
120
121
        // string $data is expected to be $ref uri
122 3171
        if (is_string($data)) {
123 4
            $data = (object)array(self::REF => $data);
124
        }
125
126 3171
        $data = self::unboolSchema($data);
127 3171
        if ($data instanceof Schema) {
128 72
            return $data;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $data; (Swaggest\JsonSchema\Schema) is incompatible with the return type of the parent method Swaggest\JsonSchema\Stru...\ClassStructure::import of type Swaggest\JsonSchema\Structure\ClassStructureTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
129
        }
130
131 3099
        return parent::import($data, $options);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return parent::import($data, $options); (Swaggest\JsonSchema\Schema) is incompatible with the return type of the parent method Swaggest\JsonSchema\Stru...\ClassStructure::import of type Swaggest\JsonSchema\Structure\ClassStructureTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
132
    }
133
134
    /**
135
     * @param mixed $data
136
     * @param Context|null $options
137
     * @return array|mixed|null|object|\stdClass
138
     * @throws Exception
139
     * @throws InvalidValue
140
     * @throws \Exception
141
     */
142 3191
    public function in($data, Context $options = null)
143
    {
144 3191
        if (null !== $this->__booleanSchema) {
145 72
            if ($this->__booleanSchema) {
146 36
                return $data;
147 36
            } elseif (empty($options->skipValidation)) {
148 18
                $this->fail(new InvalidValue('Denied by false schema'), '#');
149
            }
150
        }
151
152 3137
        if ($options === null) {
153 45
            $options = new Context();
154
        }
155
156 3137
        $options->import = true;
157
158 3137
        if ($options->refResolver === null) {
159 2818
            $options->refResolver = new RefResolver($data);
160
        } else {
161 1693
            $options->refResolver->setRootData($data);
162
        }
163
164 3137
        if ($options->remoteRefProvider) {
165 3077
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
166
        }
167
168 3137
        if ($options->import) {
169 3137
            $options->refResolver->preProcessReferences($data, $options);
170
        }
171
172 3137
        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 2360
    public function out($data, Context $options = null)
184
    {
185 2360
        if ($options === null) {
186 951
            $options = new Context();
187
        }
188
189 2360
        $options->circularReferences = new \SplObjectStorage();
190 2360
        $options->import = false;
191 2360
        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 3119
    private function processType($data, Context $options, $path = '#')
202
    {
203 3119
        if ($options->tolerateStrings && is_string($data)) {
204
            $valid = Type::readString($this->type, $data);
205
        } else {
206 3119
            $valid = Type::isValid($this->type, $data, $options->version);
207
        }
208 3119
        if (!$valid) {
209 673
            $this->fail(new TypeException(ucfirst(
210 673
                    implode(', ', is_array($this->type) ? $this->type : array($this->type))
211 673
                    . ' expected, ' . json_encode($data) . ' received')
212 673
            ), $path);
213
        }
214 3117
    }
215
216
    /**
217
     * @param mixed $data
218
     * @param string $path
219
     * @throws InvalidValue
220
     * @throws \Exception
221
     */
222 1457
    private function processEnum($data, $path = '#')
223
    {
224 1457
        $enumOk = false;
225 1457
        foreach ($this->enum as $item) {
226 1457
            if ($item === $data) { // todo support complex structures here
227 1443
                $enumOk = true;
228 1457
                break;
229
            }
230
        }
231 1457
        if (!$enumOk) {
232 89
            $this->fail(new EnumException('Enum failed'), $path);
233
        }
234 1443
    }
235
236
    /**
237
     * @param mixed $data
238
     * @param string $path
239
     * @throws InvalidValue
240
     * @throws \Swaggest\JsonDiff\Exception
241
     */
242 43
    private function processConst($data, $path)
243
    {
244 43
        if ($this->const !== $data) {
245 37
            if ((is_object($this->const) && is_object($data))
246 37
                || (is_array($this->const) && is_array($data))) {
247 15
                $diff = new JsonDiff($this->const, $data,
248 15
                    JsonDiff::STOP_ON_DIFF);
249 15
                if ($diff->getDiffCnt() != 0) {
250 15
                    $this->fail(new ConstException('Const failed'), $path);
251
                }
252
            } else {
253 22
                $this->fail(new ConstException('Const failed'), $path);
254
            }
255
        }
256 20
    }
257
258
    /**
259
     * @param mixed $data
260
     * @param Context $options
261
     * @param string $path
262
     * @throws InvalidValue
263
     * @throws \Exception
264
     * @throws \Swaggest\JsonDiff\Exception
265
     */
266 68
    private function processNot($data, Context $options, $path)
267
    {
268 68
        $exception = false;
269
        try {
270 68
            self::unboolSchema($this->not)->process($data, $options, $path . '->not');
271 15
        } catch (InvalidValue $exception) {
272
            // Expected exception
273
        }
274 68
        if ($exception === false) {
275 55
            $this->fail(new LogicException('Failed due to logical constraint: not'), $path);
276
        }
277 15
    }
278
279
    /**
280
     * @param string $data
281
     * @param string $path
282
     * @throws InvalidValue
283
     */
284 2227
    private function processString($data, $path)
285
    {
286 2227
        if ($this->minLength !== null) {
287 38
            if (mb_strlen($data, 'UTF-8') < $this->minLength) {
288 9
                $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
289
            }
290
        }
291 2221
        if ($this->maxLength !== null) {
292 43
            if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
293 19
                $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
294
            }
295
        }
296 2218
        if ($this->pattern !== null) {
297 17
            if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
298 4
                $this->fail(new StringException(json_encode($data) . ' does not match to '
299 4
                    . $this->pattern, StringException::PATTERN_MISMATCH), $path);
300
            }
301
        }
302 2218
        if ($this->format !== null) {
303 401
            $validationError = Format::validationError($this->format, $data);
304 401
            if ($validationError !== null) {
305 137
                if ($this->format === "uri" && substr($path, -3) === ':id') {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
306
                } else {
307 122
                    $this->fail(new StringException($validationError), $path);
308
                }
309
            }
310
        }
311 2218
    }
312
313
    /**
314
     * @param float|int $data
315
     * @param string $path
316
     * @throws InvalidValue
317
     */
318 1037
    private function processNumeric($data, $path)
319
    {
320 1037
        if ($this->multipleOf !== null) {
321 39
            $div = $data / $this->multipleOf;
322 39
            if ($div != (int)$div) {
323 15
                $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
324
            }
325
        }
326
327 1037
        if ($this->exclusiveMaximum !== null && !is_bool($this->exclusiveMaximum)) {
328 32
            if ($data >= $this->exclusiveMaximum) {
329 18
                $this->fail(new NumericException(
330 18
                    'Value less or equal than ' . $this->exclusiveMaximum . ' expected, ' . $data . ' received',
331 18
                    NumericException::MAXIMUM), $path);
332
            }
333
        }
334
335 1037
        if ($this->exclusiveMinimum !== null && !is_bool($this->exclusiveMinimum)) {
336 24
            if ($data <= $this->exclusiveMinimum) {
337 12
                $this->fail(new NumericException(
338 12
                    'Value more or equal than ' . $this->exclusiveMinimum . ' expected, ' . $data . ' received',
339 12
                    NumericException::MINIMUM), $path);
340
            }
341
        }
342
343 1037
        if ($this->maximum !== null) {
344 43
            if ($this->exclusiveMaximum === true) {
345 3
                if ($data >= $this->maximum) {
346 2
                    $this->fail(new NumericException(
347 2
                        'Value less or equal than ' . $this->maximum . ' expected, ' . $data . ' received',
348 3
                        NumericException::MAXIMUM), $path);
349
                }
350
            } else {
351 40
                if ($data > $this->maximum) {
352 13
                    $this->fail(new NumericException(
353 13
                        'Value less than ' . $this->minimum . ' expected, ' . $data . ' received',
354 13
                        NumericException::MAXIMUM), $path);
355
                }
356
            }
357
        }
358
359 1037
        if ($this->minimum !== null) {
360 515
            if ($this->exclusiveMinimum === true) {
361 93
                if ($data <= $this->minimum) {
362 2
                    $this->fail(new NumericException(
363 2
                        'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
364 93
                        NumericException::MINIMUM), $path);
365
                }
366
            } else {
367 440
                if ($data < $this->minimum) {
368 43
                    $this->fail(new NumericException(
369 43
                        'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
370 43
                        NumericException::MINIMUM), $path);
371
                }
372
            }
373
        }
374 1036
    }
375
376
    /**
377
     * @param mixed $data
378
     * @param Context $options
379
     * @param string $path
380
     * @return array|mixed|null|object|\stdClass
381
     * @throws InvalidValue
382
     * @throws \Exception
383
     * @throws \Swaggest\JsonDiff\Exception
384
     */
385 99
    private function processOneOf($data, Context $options, $path)
386
    {
387 99
        $successes = 0;
388 99
        $failures = '';
389 99
        $skipValidation = false;
390 99
        if ($options->skipValidation) {
391 41
            $skipValidation = true;
392 41
            $options->skipValidation = false;
393
        }
394
395 99
        $result = $data;
396 99
        foreach ($this->oneOf as $index => $item) {
397
            try {
398 99
                $result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf:' . $index);
399 78
                $successes++;
400 78
                if ($successes > 1 || $options->skipValidation) {
401 78
                    break;
402
                }
403 67
            } catch (InvalidValue $exception) {
404 99
                $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
405
                // Expected exception
406
            }
407
        }
408 99
        if ($skipValidation) {
409 41
            $options->skipValidation = true;
410 41
            if ($successes === 0) {
411 8
                $result = self::unboolSchema($this->oneOf[0])->process($data, $options, $path . '->oneOf:' . 0);
412
            }
413
        }
414
415 99
        if (!$options->skipValidation) {
416 58
            if ($successes === 0) {
417 13
                $this->fail(new LogicException('Failed due to logical constraint: no valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}"), $path);
418 45
            } elseif ($successes > 1) {
419 16
                $this->fail(new LogicException('Failed due to logical constraint: '
420 16
                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf'), $path);
421
            }
422
        }
423 70
        return $result;
424
    }
425
426
    /**
427
     * @param mixed $data
428
     * @param Context $options
429
     * @param string $path
430
     * @return array|mixed|null|object|\stdClass
431
     * @throws InvalidValue
432
     * @throws \Exception
433
     * @throws \Swaggest\JsonDiff\Exception
434
     */
435 1703
    private function processAnyOf($data, Context $options, $path)
436
    {
437 1703
        $successes = 0;
438 1703
        $failures = '';
439 1703
        $result = $data;
440 1703
        foreach ($this->anyOf as $index => $item) {
441
            try {
442 1703
                $result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf:' . $index);
443 1697
                $successes++;
444 1697
                if ($successes) {
445 1697
                    break;
446
                }
447 428
            } catch (InvalidValue $exception) {
448 428
                $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
449
                // Expected exception
450
            }
451
        }
452 1703
        if (!$successes && !$options->skipValidation) {
453 28
            $this->fail(new LogicException('Failed due to logical constraint: no valid results for anyOf {' . "\n" . substr(Helper::padLines(' ', $failures), 0, -1) . "\n}"), $path);
454
        }
455 1697
        return $result;
456
    }
457
458
    /**
459
     * @param mixed $data
460
     * @param Context $options
461
     * @param string $path
462
     * @return array|mixed|null|object|\stdClass
463
     * @throws InvalidValue
464
     * @throws \Exception
465
     * @throws \Swaggest\JsonDiff\Exception
466
     */
467 351
    private function processAllOf($data, Context $options, $path)
468
    {
469 351
        $result = $data;
470 351
        foreach ($this->allOf as $index => $item) {
471 351
            $result = self::unboolSchema($item)->process($data, $options, $path . '->allOf' . $index);
472
        }
473 313
        return $result;
474
    }
475
476
    /**
477
     * @param mixed $data
478
     * @param Context $options
479
     * @param string $path
480
     * @return array|mixed|null|object|\stdClass
481
     * @throws InvalidValue
482
     * @throws \Exception
483
     * @throws \Swaggest\JsonDiff\Exception
484
     */
485 26
    private function processIf($data, Context $options, $path)
486
    {
487 26
        $valid = true;
488
        try {
489 26
            self::unboolSchema($this->if)->process($data, $options, $path . '->if');
490 13
        } catch (InvalidValue $exception) {
491 13
            $valid = false;
492
        }
493 26
        if ($valid) {
494 18
            if ($this->then !== null) {
495 18
                return self::unboolSchema($this->then)->process($data, $options, $path . '->then');
496
            }
497
        } else {
498 13
            if ($this->else !== null) {
499 6
                return self::unboolSchema($this->else)->process($data, $options, $path . '->else');
500
            }
501
        }
502 10
        return null;
503
    }
504
505
    /**
506
     * @param \stdClass $data
507
     * @param Context $options
508
     * @param string $path
509
     * @throws InvalidValue
510
     */
511 159
    private function processObjectRequired($data, Context $options, $path)
512
    {
513 159
        if (isset($this->__dataToProperty[$options->mapping])) {
514 2
            if ($options->import) {
515 1
                foreach ($this->required as $item) {
516 1
                    if (isset($this->__propertyToData[$options->mapping][$item])) {
517 1
                        $item = $this->__propertyToData[$options->mapping][$item];
518
                    }
519 1
                    if (!property_exists($data, $item)) {
520 1
                        $this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path);
521
                    }
522
                }
523
            } else {
524 2
                foreach ($this->required as $item) {
525 2
                    if (isset($this->__dataToProperty[$options->mapping][$item])) {
526
                        $item = $this->__dataToProperty[$options->mapping][$item];
527
                    }
528 2
                    if (!property_exists($data, $item)) {
529 2
                        $this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path);
530
                    }
531
                }
532
            }
533
534
        } else {
535 158
            foreach ($this->required as $item) {
536 154
                if (!property_exists($data, $item)) {
537 154
                    $this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path);
538
                }
539
            }
540
        }
541 127
    }
542
543
    /**
544
     * @param \stdClass $data
545
     * @param Context $options
546
     * @param string $path
547
     * @param ObjectItemContract|null $result
548
     * @return array|null|ClassStructure|ObjectItemContract
549
     * @throws InvalidValue
550
     * @throws \Exception
551
     * @throws \Swaggest\JsonDiff\Exception
552
     */
553 3132
    private function processObject($data, Context $options, $path, $result = null)
554
    {
555 3132
        $import = $options->import;
556
557 3132
        if (!$options->skipValidation && $this->required !== null) {
558 159
            $this->processObjectRequired($data, $options, $path);
559
        }
560
561 3131
        if ($import) {
562 3119
            if (!$options->validateOnly) {
563
564 3119
                if ($this->useObjectAsArray) {
565 1
                    $result = array();
566 3118
                } elseif (!$result instanceof ObjectItemContract) {
567
                    //$result = $this->makeObjectItem($options);
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% 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...
568
                    //* todo check performance impact
569 3118
                    if (null === $this->objectItemClass) {
570 1148
                        $result = new ObjectItem();
571
                    } else {
572 3108
                        $className = $this->objectItemClass;
573 3108
                        if ($options->objectItemClassMapping !== null) {
574
                            if (isset($options->objectItemClassMapping[$className])) {
575
                                $className = $options->objectItemClassMapping[$className];
576
                            }
577
                        }
578 3108
                        $result = new $className;
579
                    }
580
                    //*/
581
582
583 3118
                    if ($result instanceof ClassStructure) {
584 3107
                        if ($result->__validateOnSet) {
585 3107
                            $result->__validateOnSet = false;
586
                            /** @noinspection PhpUnusedLocalVariableInspection */
587
                            /* 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...
588
                            $validateOnSetHandler = new ScopeExit(function () use ($result) {
589
                                $result->__validateOnSet = true;
590
                            });
591
                            //*/
592
                        }
593
                    }
594
595
                    //* todo check performance impact
596 3118
                    if ($result instanceof ObjectItemContract) {
597 3118
                        $result->setDocumentPath($path);
598
                    }
599
                    //*/
600
                }
601
            }
602
        }
603
604
        // @todo better check for schema id
605
606 3131
        if ($import
607 3131
            && isset($data->{Schema::ID_D4})
608 3131
            && ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO)
609 3131
            && is_string($data->{Schema::ID_D4}) /*&& (!isset($this->properties['id']))/* && $this->isMetaSchema($data)*/) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% 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 29
            $id = $data->{Schema::ID_D4};
611 29
            $refResolver = $options->refResolver;
612 29
            $parentScope = $refResolver->updateResolutionScope($id);
613
            /** @noinspection PhpUnusedLocalVariableInspection */
614
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
615 29
                $refResolver->setResolutionScope($parentScope);
616 29
            });
617
        }
618
619 3131
        if ($import
620 3131
            && isset($data->{self::ID})
621 3131
            && ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO)
622 3131
            && is_string($data->{self::ID}) /*&& (!isset($this->properties['id']))/* && $this->isMetaSchema($data)*/) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% 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...
623 114
            $id = $data->{self::ID};
624 114
            $refResolver = $options->refResolver;
625 114
            $parentScope = $refResolver->updateResolutionScope($id);
626
            /** @noinspection PhpUnusedLocalVariableInspection */
627
            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
628 114
                $refResolver->setResolutionScope($parentScope);
629 114
            });
630
        }
631
632 3131
        if ($import) {
633
            try {
634
                while (
635 3119
                    isset($data->{self::REF})
636 3119
                    && is_string($data->{self::REF})
637 3119
                    && !isset($this->properties[self::REF])
638
                ) {
639 407
                    $refString = $data->{self::REF};
640
641
                    // todo check performance impact
642 407
                    if ($refString === 'http://json-schema.org/draft-04/schema#'
643 397
                        || $refString === 'http://json-schema.org/draft-06/schema#'
644 407
                        || $refString === 'http://json-schema.org/draft-07/schema#') {
645 26
                        return Schema::schema();
646
                    }
647
648
                    // TODO consider process # by reference here ?
649 381
                    $refResolver = $options->refResolver;
650 381
                    $preRefScope = $refResolver->getResolutionScope();
651
                    /** @noinspection PhpUnusedLocalVariableInspection */
652 381
                    $deferRefScope = new ScopeExit(function () use ($preRefScope, $refResolver) {
653 381
                        $refResolver->setResolutionScope($preRefScope);
654 381
                    });
655
656 381
                    $ref = $refResolver->resolveReference($refString);
657 381
                    $data = self::unboolSchemaData($ref->getData());
658
                    //$data = $ref->getData();
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
659 381
                    if (!$options->validateOnly) {
660 381
                        if ($ref->isImported()) {
661 172
                            $refResult = $ref->getImported();
662 172
                            return $refResult;
663
                        }
664 381
                        if ($result instanceof ObjectItemContract) {
665 381
                            $result->setFromRef($refString);
666
                        }
667 381
                        $ref->setImported($result);
668 381
                        $refResult = $this->process($data, $options, $path . '->ref:' . $refString, $result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type array or object; however, Swaggest\JsonSchema\Schema::process() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
669 381
                        $ref->setImported($refResult);
670 381
                        return $refResult;
671
                    } else {
672
                        $this->process($data, $options, $path . '->ref:' . $refString);
673
                    }
674
                }
675
            } catch (InvalidValue $exception) {
676
                $this->fail($exception, $path);
677
            }
678
        }
679
680
        /** @var Schema[]|null $properties */
681 3131
        $properties = null;
682
683 3131
        $nestedProperties = null;
684 3131
        if ($this->properties !== null) {
685 3117
            $properties = $this->properties->toArray(); // todo call directly
686 3117
            if ($this->properties instanceof Properties) {
687 3117
                $nestedProperties = $this->properties->nestedProperties;
688
            } else {
689 571
                $nestedProperties = array();
690
            }
691
        }
692
693 3131
        $array = array();
694 3131
        if (!empty($this->__dataToProperty[$options->mapping])) { // todo skip on $options->validateOnly
695 3101
            foreach ((array)$data as $key => $value) {
696 3097
                if ($import) {
697 3096
                    if (isset($this->__dataToProperty[$options->mapping][$key])) {
698 3096
                        $key = $this->__dataToProperty[$options->mapping][$key];
699
                    }
700
                } else {
701 20
                    if (isset($this->__propertyToData[$options->mapping][$key])) {
702 1
                        $key = $this->__propertyToData[$options->mapping][$key];
703
                    }
704
                }
705 3101
                $array[$key] = $value;
706
            }
707
        } else {
708 1224
            $array = (array)$data;
709
        }
710
711 3131
        if (!$options->skipValidation) {
712 3110
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
713 4
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
714
            }
715 3110
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
716 3
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
717
            }
718 3110
            if ($this->propertyNames !== null) {
719 17
                $propertyNames = self::unboolSchema($this->propertyNames);
720 17
                foreach ($array as $key => $tmp) {
721 10
                    $propertyNames->process($key, $options, $path . '->propertyNames:' . $key);
722
                }
723
            }
724
        }
725
726 3131
        $defaultApplied = array();
727 3131
        if ($import
728 3131
            && !$options->validateOnly
729 3131
            && $options->applyDefaults
730 3131
            && $properties !== null
731 3131
            && $this->objectItemClass !== 'Swaggest\JsonSchema\Schema' // todo replace literal
732
        ) {
733 350
            foreach ($properties as $key => $property) {
734 348
                if (isset($property->default)) {
735 17
                    if (isset($this->__dataToProperty[$options->mapping][$key])) {
736
                        $key = $this->__dataToProperty[$options->mapping][$key];
737
                    }
738 17
                    if (!array_key_exists($key, $array)) {
739 11
                        $defaultApplied[$key] = true;
740 348
                        $array[$key] = $property->default;
741
                    }
742
                }
743
            }
744
        }
745
746 3131
        foreach ($array as $key => $value) {
747 3121
            if ($key === '' && PHP_VERSION_ID < 71000) {
748 1
                $this->fail(new InvalidValue('Empty property name'), $path);
749
            }
750
751 3120
            $found = false;
752
753 3120
            if (!$options->skipValidation && !empty($this->dependencies)) {
754 73
                $deps = $this->dependencies;
755 73
                if (isset($deps->$key)) {
756 63
                    $dependencies = $deps->$key;
757 63
                    $dependencies = self::unboolSchema($dependencies);
758 63
                    if ($dependencies instanceof Schema) {
759 29
                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
760
                    } else {
761 34
                        foreach ($dependencies as $item) {
762 31
                            if (!property_exists($data, $item)) {
763 18
                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
764 31
                                    ObjectException::DEPENDENCY_MISSING), $path);
765
                            }
766
                        }
767
                    }
768
                }
769
            }
770
771 3120
            $propertyFound = false;
772 3120
            if (isset($properties[$key])) {
773
                /** @var Schema[] $properties */
774 3109
                $prop = self::unboolSchema($properties[$key]);
775 3109
                $propertyFound = true;
776 3109
                $found = true;
777 3109
                if ($prop instanceof Schema) {
778 3052
                    $value = $prop->process(
779 3052
                        $value,
780 3052
                        isset($defaultApplied[$key]) ? $options->withSkipValidation() : $options,
781 3052
                        $path . '->properties:' . $key
782
                    );
783
                }
784
            }
785
786
            /** @var Egg[] $nestedEggs */
787 3115
            $nestedEggs = null;
788 3115
            if (isset($nestedProperties[$key])) {
789 6
                $found = true;
790 6
                $nestedEggs = $nestedProperties[$key];
791
                // todo iterate all nested props?
792 6
                $value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key);
793
            }
794
795 3115
            if ($this->patternProperties !== null) {
796 164
                foreach ($this->patternProperties as $pattern => $propertySchema) {
797 164
                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
798 118
                        $found = true;
799 118
                        $value = self::unboolSchema($propertySchema)->process($value, $options,
800 118
                            $path . '->patternProperties[' . $pattern . ']:' . $key);
801 92
                        if (!$options->validateOnly && $import) {
802 143
                            $result->addPatternPropertyName($pattern, $key);
803
                        }
804
                        //break; // todo manage multiple import data properly (pattern accessor)
805
                    }
806
                }
807
            }
808 3115
            if (!$found && $this->additionalProperties !== null) {
809 986
                if (!$options->skipValidation && $this->additionalProperties === false) {
810 11
                    $this->fail(new ObjectException('Additional properties not allowed'), $path . ':' . $key);
811
                }
812
813 986
                if ($this->additionalProperties instanceof Schema) {
814 986
                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
815
                }
816
817 979
                if ($import && !$this->useObjectAsArray && !$options->validateOnly) {
818 978
                    $result->addAdditionalPropertyName($key);
819
                }
820
            }
821
822 3111
            if (!$options->validateOnly && $nestedEggs && $import) {
823 5
                foreach ($nestedEggs as $nestedEgg) {
824 5
                    $result->setNestedProperty($key, $value, $nestedEgg);
825
                }
826 5
                if ($propertyFound) {
827 5
                    $result->$key = $value;
828
                }
829
            } else {
830 3110
                if ($this->useObjectAsArray && $import) {
831 1
                    $result[$key] = $value;
832
                } else {
833 3110
                    if ($found || !$import) {
834 3108
                        $result->$key = $value;
835 1134
                    } elseif (!isset($result->$key)) {
836 3111
                        $result->$key = $value;
837
                    }
838
                }
839
            }
840
        }
841
842 3119
        return $result;
843
    }
844
845
    /**
846
     * @param array $data
847
     * @param Context $options
848
     * @param string $path
849
     * @param array $result
850
     * @return mixed
851
     * @throws InvalidValue
852
     * @throws \Exception
853
     * @throws \Swaggest\JsonDiff\Exception
854
     */
855 1200
    private function processArray($data, Context $options, $path, $result)
856
    {
857 1200
        $count = count($data);
858 1200
        if (!$options->skipValidation) {
859 1000
            if ($this->minItems !== null && $count < $this->minItems) {
860 9
                $this->fail(new ArrayException("Not enough items in array"), $path);
861
            }
862
863 994
            if ($this->maxItems !== null && $count > $this->maxItems) {
864 6
                $this->fail(new ArrayException("Too many items in array"), $path);
865
            }
866
        }
867
868 1188
        $pathItems = 'items';
869 1188
        $this->items = self::unboolSchema($this->items);
870 1188
        if ($this->items instanceof Schema) {
871 842
            $items = array();
872 842
            $additionalItems = $this->items;
873 681
        } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
874 681
            $items = array();
875 681
            $additionalItems = true;
876
        } else { // listed items
877 104
            $items = $this->items;
878 104
            $additionalItems = $this->additionalItems;
879 104
            $pathItems = 'additionalItems';
880
        }
881
882
        /**
883
         * @var Schema|Schema[] $items
884
         * @var null|bool|Schema $additionalItems
885
         */
886 1188
        $itemsLen = is_array($items) ? count($items) : 0;
887 1188
        $index = 0;
888 1188
        foreach ($result as $key => $value) {
889 1063
            if ($index < $itemsLen) {
890 94
                $itemSchema = self::unboolSchema($items[$index]);
891 94
                $result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
892
            } else {
893 1063
                if ($additionalItems instanceof Schema) {
894 816
                    $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
895 816
                        . '[' . $index . ']');
896 565
                } elseif (!$options->skipValidation && $additionalItems === false) {
897 6
                    $this->fail(new ArrayException('Unexpected array item'), $path);
898
                }
899
            }
900 1043
            ++$index;
901
        }
902
903 1164
        if (!$options->skipValidation && $this->uniqueItems) {
904 498
            if (!UniqueItems::isValid($data)) {
905 19
                $this->fail(new ArrayException('Array is not unique'), $path);
906
            }
907
        }
908
909 1145
        if (!$options->skipValidation && $this->contains !== null) {
910
            /** @var Schema|bool $contains */
911 36
            $contains = $this->contains;
912 36
            if ($contains === false) {
913 4
                $this->fail(new ArrayException('Contains is false'), $path);
914
            }
915 32
            if ($count === 0) {
916 7
                $this->fail(new ArrayException('Empty array fails contains constraint'), $path);
917
            }
918 25
            if ($contains === true) {
919 2
                $contains = self::unboolSchema($contains);
920
            }
921 25
            $containsOk = false;
922 25
            foreach ($data as $key => $item) {
923
                try {
924 25
                    $contains->process($item, $options, $path . '->' . $key);
925 17
                    $containsOk = true;
926 17
                    break;
927 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...
928
                }
929
            }
930 25
            if (!$containsOk) {
931 8
                $this->fail(new ArrayException('Array fails contains constraint'), $path);
932
            }
933
        }
934 1126
        return $result;
935
    }
936
937
    /**
938
     * @param mixed|string $data
939
     * @param Context $options
940
     * @param string $path
941
     * @return bool|mixed|string
942
     * @throws InvalidValue
943
     */
944 14
    private function processContent($data, Context $options, $path)
945
    {
946
        try {
947 14
            if ($options->unpackContentMediaType) {
948 7
                return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import);
949
            } else {
950 7
                Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true);
951
            }
952 4
        } catch (InvalidValue $exception) {
953 4
            $this->fail($exception, $path);
954
        }
955 7
        return $data;
956
    }
957
958
    /**
959
     * @param mixed $data
960
     * @param Context $options
961
     * @param string $path
962
     * @param null $result
963
     * @return array|mixed|null|object|\stdClass
964
     * @throws InvalidValue
965
     * @throws \Exception
966
     * @throws \Swaggest\JsonDiff\Exception
967
     */
968 3178
    public function process($data, Context $options, $path = '#', $result = null)
969
    {
970
971 3178
        $import = $options->import;
972
        //$pathTrace = explode('->', $path);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% 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...
973
974 3178
        if (!$import && $data instanceof ObjectItemContract) {
975 779
            $result = new \stdClass();
976 779
            if ($options->circularReferences->contains($data)) {
977
                /** @noinspection PhpIllegalArrayKeyTypeInspection */
978 1
                $path = $options->circularReferences[$data];
979
                // @todo $path is not a valid json pointer $ref
980 1
                $result->{self::REF} = $path;
981 1
                return $result;
982
//                return $options->circularReferences[$data];
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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...
983
            }
984 779
            $options->circularReferences->attach($data, $path);
985
            //$options->circularReferences->attach($data, $result);
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
986
987 779
            $data = $data->jsonSerialize();
988
        }
989 3178
        if (!$import && is_array($data) && $this->useObjectAsArray) {
990 1
            $data = (object)$data;
991
        }
992
993 3178
        if (null !== $options->dataPreProcessor) {
994
            $data = $options->dataPreProcessor->process($data, $this, $import);
995
        }
996
997 3178
        if ($result === null) {
998 3178
            $result = $data;
999
        }
1000
1001 3178
        if ($options->skipValidation) {
1002 1422
            goto skipValidation;
1003
        }
1004
1005 3140
        if ($this->type !== null) {
1006 3119
            $this->processType($data, $options, $path);
1007
        }
1008
1009 3139
        if ($this->enum !== null) {
1010 1457
            $this->processEnum($data, $path);
1011
        }
1012
1013 3139
        if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) {
1014 43
            $this->processConst($data, $path);
1015
        }
1016
1017 3139
        if ($this->not !== null) {
1018 68
            $this->processNot($data, $options, $path);
1019
        }
1020
1021 3139
        if (is_string($data)) {
1022 2227
            $this->processString($data, $path);
1023
        }
1024
1025 3139
        if (is_int($data) || is_float($data)) {
1026 1037
            $this->processNumeric($data, $path);
1027
        }
1028
1029 3139
        if ($this->if !== null) {
1030 26
            $result = $this->processIf($data, $options, $path);
1031
        }
1032
1033
        skipValidation:
1034
1035 3177
        if ($this->oneOf !== null) {
1036 99
            $result = $this->processOneOf($data, $options, $path);
1037
        }
1038
1039 3177
        if ($this->anyOf !== null) {
1040 1703
            $result = $this->processAnyOf($data, $options, $path);
1041
        }
1042
1043 3177
        if ($this->allOf !== null) {
1044 351
            $result = $this->processAllOf($data, $options, $path);
1045
        }
1046
1047 3177
        if ($data instanceof \stdClass) {
1048 3132
            $result = $this->processObject($data, $options, $path, $result);
1049
        }
1050
1051 3174
        if (is_array($data)) {
1052 1200
            $result = $this->processArray($data, $options, $path, $result);
1053
        }
1054
1055 3174
        if ($this->contentEncoding !== null || $this->contentMediaType !== null) {
1056 14
            $result = $this->processContent($data, $options, $path);
1057
        }
1058
1059 3174
        return $result;
1060
    }
1061
1062
    /**
1063
     * @param boolean $useObjectAsArray
1064
     * @return Schema
1065
     */
1066 1
    public function setUseObjectAsArray($useObjectAsArray)
1067
    {
1068 1
        $this->useObjectAsArray = $useObjectAsArray;
1069 1
        return $this;
1070
    }
1071
1072
    /**
1073
     * @param InvalidValue $exception
1074
     * @param string $path
1075
     * @throws InvalidValue
1076
     */
1077 1211
    private function fail(InvalidValue $exception, $path)
1078
    {
1079 1211
        if ($path !== '#') {
1080 729
            $exception->addPath($path);
1081
        }
1082 1211
        throw $exception;
1083
    }
1084
1085 14
    public static function integer()
1086
    {
1087 14
        $schema = new static();
1088 14
        $schema->type = Type::INTEGER;
1089 14
        return $schema;
1090
    }
1091
1092 5
    public static function number()
1093
    {
1094 5
        $schema = new static();
1095 5
        $schema->type = Type::NUMBER;
1096 5
        return $schema;
1097
    }
1098
1099 9
    public static function string()
1100
    {
1101 9
        $schema = new static();
1102 9
        $schema->type = Type::STRING;
1103 9
        return $schema;
1104
    }
1105
1106 5
    public static function boolean()
1107
    {
1108 5
        $schema = new static();
1109 5
        $schema->type = Type::BOOLEAN;
1110 5
        return $schema;
1111
    }
1112
1113 7
    public static function object()
1114
    {
1115 7
        $schema = new static();
1116 7
        $schema->type = Type::OBJECT;
1117 7
        return $schema;
1118
    }
1119
1120 2
    public static function arr()
1121
    {
1122 2
        $schema = new static();
1123 2
        $schema->type = Type::ARR;
1124 2
        return $schema;
1125
    }
1126
1127
    public static function null()
1128
    {
1129
        $schema = new static();
1130
        $schema->type = Type::NULL;
1131
        return $schema;
1132
    }
1133
1134
1135
    /**
1136
     * @param Properties $properties
1137
     * @return Schema
1138
     */
1139 3
    public function setProperties($properties)
1140
    {
1141 3
        $this->properties = $properties;
1142 3
        return $this;
1143
    }
1144
1145
    /**
1146
     * @param string $name
1147
     * @param Schema $schema
1148
     * @return $this
1149
     */
1150 3
    public function setProperty($name, $schema)
1151
    {
1152 3
        if (null === $this->properties) {
1153 3
            $this->properties = new Properties();
1154
        }
1155 3
        $this->properties->__set($name, $schema);
1156 3
        return $this;
1157
    }
1158
1159
    /** @var Meta[] */
1160
    private $metaItems = array();
1161
1162 1
    public function addMeta(Meta $meta)
1163
    {
1164 1
        $this->metaItems[get_class($meta)] = $meta;
1165 1
        return $this;
1166
    }
1167
1168 1
    public function getMeta($className)
1169
    {
1170 1
        if (isset($this->metaItems[$className])) {
1171 1
            return $this->metaItems[$className];
1172
        }
1173
        return null;
1174
    }
1175
1176
    /**
1177
     * @param Context $options
1178
     * @return ObjectItemContract
1179
     */
1180 5
    public function makeObjectItem(Context $options = null)
1181
    {
1182 5
        if (null === $this->objectItemClass) {
1183
            return new ObjectItem();
1184
        } else {
1185 5
            $className = $this->objectItemClass;
1186 5
            if ($options !== null) {
1187
                if (isset($options->objectItemClassMapping[$className])) {
1188
                    $className = $options->objectItemClassMapping[$className];
1189
                }
1190
            }
1191 5
            return new $className;
1192
        }
1193
    }
1194
1195
    /**
1196
     * @param mixed $schema
1197
     * @return mixed|Schema
1198
     */
1199 3190
    private static function unboolSchema($schema)
1200
    {
1201 3190
        static $trueSchema;
1202 3190
        static $falseSchema;
1203
1204 3190
        if (null === $trueSchema) {
1205 1
            $trueSchema = new Schema();
1206 1
            $trueSchema->__booleanSchema = true;
1207 1
            $falseSchema = new Schema();
1208 1
            $falseSchema->not = $trueSchema;
1209 1
            $falseSchema->__booleanSchema = false;
1210
        }
1211
1212 3190
        if ($schema === true) {
1213 108
            return $trueSchema;
1214 3162
        } elseif ($schema === false) {
1215 94
            return $falseSchema;
1216
        } else {
1217 3130
            return $schema;
1218
        }
1219
    }
1220
1221
    /**
1222
     * @param mixed $data
1223
     * @return \stdClass
1224
     */
1225 381
    private static function unboolSchemaData($data)
1226
    {
1227 381
        static $trueSchema;
1228 381
        static $falseSchema;
1229
1230 381
        if (null === $trueSchema) {
1231 1
            $trueSchema = new \stdClass();
1232 1
            $falseSchema = new \stdClass();
1233 1
            $falseSchema->not = $trueSchema;
1234
        }
1235
1236 381
        if ($data === true) {
1237 6
            return $trueSchema;
1238 377
        } elseif ($data === false) {
1239 5
            return $falseSchema;
1240
        } else {
1241 372
            return $data;
1242
        }
1243
    }
1244
1245
}
1246