Completed
Push — master ( 8b004e...a53ad0 )
by Viacheslav
01:35
created

Schema::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
namespace Swaggest\JsonSchema;
4
5
6
use PhpLang\ScopeExit;
7
use Swaggest\JsonSchema\Constraint\Properties;
8
use Swaggest\JsonSchema\Constraint\Type;
9
use Swaggest\JsonSchema\Constraint\UniqueItems;
10
use Swaggest\JsonSchema\Exception\ArrayException;
11
use Swaggest\JsonSchema\Exception\EnumException;
12
use Swaggest\JsonSchema\Exception\LogicException;
13
use Swaggest\JsonSchema\Exception\NumericException;
14
use Swaggest\JsonSchema\Exception\ObjectException;
15
use Swaggest\JsonSchema\Exception\StringException;
16
use Swaggest\JsonSchema\Exception\TypeException;
17
use Swaggest\JsonSchema\Structure\ClassStructure;
18
use Swaggest\JsonSchema\Structure\Egg;
19
use Swaggest\JsonSchema\Structure\ObjectItem;
20
21
/**
22
 * Class Schema
23
 * @package Swaggest\JsonSchema
24
 */
25
class Schema extends JsonSchema
26
{
27
    const SCHEMA_DRAFT_04_URL = 'http://json-schema.org/draft-04/schema';
28
29
    const REF = '$ref';
30
31
32
    /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% 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...
33
    public $__seqId;
34
    public static $seq = 0;
35
36
    public function __construct()
37
    {
38
        self::$seq++;
39
        $this->__seqId = self::$seq;
40
    }
41
    //*/
42
43
    // Object
44
    /** @var Properties|Schema[] */
45
    public $properties;
46
    /** @var Schema|bool */
47
    public $additionalProperties;
48
    /** @var Schema[] */
49
    public $patternProperties;
50
    /** @var string[][]|Schema[] */
51
    public $dependencies;
52
53
    // Array
54
    /** @var Schema|Schema[] */
55
    public $items;
56
    /** @var Schema|bool */
57
    public $additionalItems;
58
59
    const FORMAT_DATE_TIME = 'date-time'; // todo implement
60
61
62
    /** @var Schema[] */
63
    public $allOf;
64
    /** @var Schema */
65
    public $not;
66
    /** @var Schema[] */
67
    public $anyOf;
68
    /** @var Schema[] */
69
    public $oneOf;
70
71
    public $objectItemClass;
72
    private $useObjectAsArray = false;
73
74
    private $__dataToProperty = array();
75
    private $__propertyToData = array();
76
77
78
    public function addPropertyMapping($dataName, $propertyName)
79
    {
80
        $this->__dataToProperty[$dataName] = $propertyName;
81
        $this->__propertyToData[$propertyName] = $dataName;
82
        return $this;
83
    }
84
85
    private function preProcessReferences($data, Context $options = null)
86
    {
87
        if (is_array($data)) {
88
            foreach ($data as $key => $item) {
89
                $this->preProcessReferences($item, $options);
90
            }
91
        } elseif ($data instanceof \stdClass) {
92
            /** @var JsonSchema $data */
93
            if (isset($data->id) && is_string($data->id)) {
94
                $prev = $options->refResolver->setupResolutionScope($data->id, $data);
95
                /** @noinspection PhpUnusedLocalVariableInspection */
96
                $_ = new ScopeExit(function () use ($prev, $options) {
0 ignored issues
show
Unused Code introduced by
$_ is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
97
                    $options->refResolver->setResolutionScope($prev);
98
                });
99
            }
100
101
            foreach ((array)$data as $key => $value) {
102
                $this->preProcessReferences($value, $options);
103
            }
104
        }
105
    }
106
107
    public function in($data, Context $options = null)
108
    {
109
        if ($options === null) {
110
            $options = new Context();
111
        }
112
113
        $options->import = true;
114
115
        $options->refResolver = new RefResolver($data);
116
        if ($options->remoteRefProvider) {
117
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
118
        }
119
120
        if ($options->import) {
121
            $this->preProcessReferences($data, $options);
122
        }
123
124
        return $this->process($data, $options, '#');
125
    }
126
127
128
    public function out($data, Context $options = null)
129
    {
130
        if ($options === null) {
131
            $options = new Context();
132
        }
133
134
        $options->circularReferences = new \SplObjectStorage();
135
        $options->import = false;
136
        return $this->process($data, $options);
137
    }
138
139
    public function process($data, Context $options, $path = '#', $result = null)
140
    {
141
142
        $import = $options->import;
143
        //$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...
144
145
        if (!$import && $data instanceof ObjectItem) {
146
            $result = new \stdClass();
147
            if ($options->circularReferences->contains($data)) {
148
                /** @noinspection PhpIllegalArrayKeyTypeInspection */
149
                $path = $options->circularReferences[$data];
150
                $result->{self::REF} = $path;
151
                return $result;
152
            }
153
            $options->circularReferences->attach($data, $path);
154
155
156
            $data = $data->jsonSerialize();
157
        }
158
        if (!$import && is_array($data) && $this->useObjectAsArray) {
159
            $data = (object)$data;
160
        }
161
162
        if (null !== $options->dataPreProcessor) {
163
            $data = $options->dataPreProcessor->process($data, $this, $import);
164
        }
165
166
        if ($result === null) {
167
            $result = $data;
168
        }
169
170
        if ($options->skipValidation) {
171
            goto skipValidation;
172
        }
173
174
        if ($this->type !== null) {
175
            if (!Type::isValid($this->type, $data)) {
176
                $this->fail(new TypeException(ucfirst(
177
                        implode(', ', is_array($this->type) ? $this->type : array($this->type))
178
                        . ' expected, ' . json_encode($data) . ' received')
179
                ), $path);
180
            }
181
        }
182
183
        if ($this->enum !== null) {
184
            $enumOk = false;
185
            foreach ($this->enum as $item) {
186
                if ($item === $data) { // todo support complex structures here
187
                    $enumOk = true;
188
                    break;
189
                }
190
            }
191
            if (!$enumOk) {
192
                $this->fail(new EnumException('Enum failed'), $path);
193
            }
194
        }
195
196
        if ($this->not !== null) {
197
            $exception = false;
198
            try {
199
                $this->not->process($data, $options, $path . '->not');
200
            } catch (InvalidValue $exception) {
201
                // Expected exception
202
            }
203
            if ($exception === false) {
204
                $this->fail(new LogicException('Failed due to logical constraint: not'), $path);
205
            }
206
        }
207
208
        if (is_string($data)) {
209
            if ($this->minLength !== null) {
210
                if (mb_strlen($data, 'UTF-8') < $this->minLength) {
211
                    $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
212
                }
213
            }
214
            if ($this->maxLength !== null) {
215
                if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
216
                    $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
217
                }
218
            }
219
            if ($this->pattern !== null) {
220
                if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
221
                    $this->fail(new StringException(json_encode($data) . ' does not match to '
222
                        . $this->pattern, StringException::PATTERN_MISMATCH), $path);
223
                }
224
            }
225
        }
226
227
        if (is_int($data) || is_float($data)) {
228
            if ($this->multipleOf !== null) {
229
                $div = $data / $this->multipleOf;
230
                if ($div != (int)$div) {
231
                    $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
232
                }
233
            }
234
235
            if ($this->maximum !== null) {
236
                if ($this->exclusiveMaximum === true) {
237
                    if ($data >= $this->maximum) {
238
                        $this->fail(new NumericException(
239
                            'Value less or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
240
                            NumericException::MAXIMUM), $path);
241
                    }
242
                } else {
243
                    if ($data > $this->maximum) {
244
                        $this->fail(new NumericException(
245
                            'Value less than ' . $this->minimum . ' expected, ' . $data . ' received',
246
                            NumericException::MAXIMUM), $path);
247
                    }
248
                }
249
            }
250
251
            if ($this->minimum !== null) {
252
                if ($this->exclusiveMinimum === true) {
253
                    if ($data <= $this->minimum) {
254
                        $this->fail(new NumericException(
255
                            'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
256
                            NumericException::MINIMUM), $path);
257
                    }
258
                } else {
259
                    if ($data < $this->minimum) {
260
                        $this->fail(new NumericException(
261
                            'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
262
                            NumericException::MINIMUM), $path);
263
                    }
264
                }
265
            }
266
        }
267
268
        skipValidation:
269
270
        if ($this->oneOf !== null) {
271
            $successes = 0;
272
            $failures = '';
273
            $skipValidation = false;
274
            if ($options->skipValidation) {
275
                $skipValidation = true;
276
                $options->skipValidation = false;
277
            }
278
279
            foreach ($this->oneOf as $index => $item) {
280
                try {
281
                    $result = $item->process($data, $options, $path . '->oneOf:' . $index);
282
                    $successes++;
283
                    if ($successes > 1 || $options->skipValidation) {
284
                        break;
285
                    }
286
                } catch (InvalidValue $exception) {
287
                    $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
288
                    // Expected exception
289
                }
290
            }
291
            if ($skipValidation) {
292
                $options->skipValidation = true;
293
                if ($successes === 0) {
294
                    $result = $this->oneOf[0]->process($data, $options, $path . '->oneOf:' . 0);
295
                }
296
            }
297
298
            if (!$options->skipValidation) {
299
                if ($successes === 0) {
300
                    $this->fail(new LogicException('Failed due to logical constraint: no valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}"), $path);
301
                } elseif ($successes > 1) {
302
                    $this->fail(new LogicException('Failed due to logical constraint: '
303
                        . $successes . '/' . count($this->oneOf) . ' valid results for oneOf'), $path);
304
                }
305
            }
306
        }
307
308
        if ($this->anyOf !== null) {
309
            $successes = 0;
310
            $failures = '';
311
            foreach ($this->anyOf as $index => $item) {
312
                try {
313
                    $result = $item->process($data, $options, $path . '->anyOf:' . $index);
314
                    $successes++;
315
                    if ($successes) {
316
                        break;
317
                    }
318
                } catch (InvalidValue $exception) {
319
                    $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
320
                    // Expected exception
321
                }
322
            }
323
            if (!$successes && !$options->skipValidation) {
324
                $this->fail(new LogicException('Failed due to logical constraint: no valid results for anyOf {' . "\n" . substr(Helper::padLines(' ', $failures), 0, -1) . "\n}"), $path);
325
            }
326
        }
327
328
        if ($this->allOf !== null) {
329
            foreach ($this->allOf as $index => $item) {
330
                $result = $item->process($data, $options, $path . '->allOf' . $index);
331
            }
332
        }
333
334
        if ($data instanceof \stdClass) {
335
            if (!$options->skipValidation && $this->required !== null) {
336
                foreach ($this->required as $item) {
337
                    if (!property_exists($data, $item)) {
338
                        $this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path);
339
                    }
340
                }
341
            }
342
343
            if ($import) {
344
                if ($this->useObjectAsArray) {
345
                    $result = array();
346
                } elseif (!$result instanceof ObjectItem) {
347
                    $result = $this->makeObjectItem($options);
348
349
                    if ($result instanceof ClassStructure) {
350
                        if ($result->__validateOnSet) {
351
                            $result->__validateOnSet = false;
352
                            /** @noinspection PhpUnusedLocalVariableInspection */
353
                            $validateOnSetHandler = new ScopeExit(function () use ($result) {
1 ignored issue
show
Unused Code introduced by
$validateOnSetHandler is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
354
                                $result->__validateOnSet = true;
355
                            });
356
                        }
357
                    }
358
359
                    if ($result instanceof ObjectItem) {
360
                        $result->__documentPath = $path;
361
                    }
362
                }
363
            }
364
365
            if ($import) {
366
                try {
367
                    while (
368
                        isset($data->{self::REF})
369
                        && is_string($data->{self::REF})
370
                        && !isset($this->properties[self::REF])
371
                    ) {
372
                        $refString = $data->{self::REF};
373
                        // TODO consider process # by reference here ?
374
                        $refResolver = $options->refResolver;
375
                        $preRefScope = $refResolver->getResolutionScope();
376
                        /** @noinspection PhpUnusedLocalVariableInspection */
377
                        $deferRefScope = new ScopeExit(function () use ($preRefScope, $refResolver) {
1 ignored issue
show
Unused Code introduced by
$deferRefScope is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
378
                            $refResolver->setResolutionScope($preRefScope);
379
                        });
380
                        $ref = $refResolver->resolveReference($refString);
381
                        if ($ref->isImported()) {
382
                            $refResult = $ref->getImported();
383
                            return $refResult;
384
                        }
385
                        $data = $ref->getData();
386
                        if ($result instanceof ObjectItem) {
387
                            $result->__fromRef = $refString;
388
                        }
389
                        $ref->setImported($result);
390
                        $refResult = $this->process($data, $options, $path . '->ref:' . $refString, $result);
391
                        $ref->setImported($refResult);
392
                        return $refResult;
393
                    }
394
                } catch (InvalidValue $exception) {
395
                    $this->fail($exception, $path);
396
                }
397
            }
398
399
            // @todo better check for schema id
400
401
            if ($import && isset($data->id) && is_string($data->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...
402
                $id = $data->id;
403
                $refResolver = $options->refResolver;
404
                $parentScope = $refResolver->updateResolutionScope($id);
405
                /** @noinspection PhpUnusedLocalVariableInspection */
406
                $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
1 ignored issue
show
Unused Code introduced by
$defer is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
407
                    $refResolver->setResolutionScope($parentScope);
408
                });
409
            }
410
411
            if ($this->properties !== null) {
412
                /** @var Schema[] $properties */
413
                $properties = &$this->properties->toArray(); // TODO check performance of pointer
414
                if ($this->properties instanceof Properties) {
415
                    $nestedProperties = $this->properties->getNestedProperties();
416
                } else {
417
                    $nestedProperties = array();
418
                }
419
            }
420
421
            $array = array();
422
            if (!empty($this->__dataToProperty)) {
423
                foreach ((array)$data as $key => $value) {
424
                    if ($import) {
425
                        if (isset($this->__dataToProperty[$key])) {
426
                            $key = $this->__dataToProperty[$key];
427
                        }
428
                    } else {
429
                        if (isset($this->__propertyToData[$key])) {
430
                            $key = $this->__propertyToData[$key];
431
                        }
432
                    }
433
                    $array[$key] = $value;
434
                }
435
            } else {
436
                $array = (array)$data;
437
            }
438
439
            if (!$options->skipValidation) {
440
                if ($this->minProperties !== null && count($array) < $this->minProperties) {
441
                    $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
442
                }
443
                if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
444
                    $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
445
                }
446
            }
447
448
            foreach ($array as $key => $value) {
449
                if ($key === '' && PHP_VERSION_ID < 71000) {
450
                    $this->fail(new InvalidValue('Empty property name'), $path);
451
                }
452
453
                $found = false;
454
455
                if (!$options->skipValidation && !empty($this->dependencies)) {
456
                    $deps = $this->dependencies;
457
                    if (isset($deps->$key)) {
458
                        $dependencies = $deps->$key;
459
                        if ($dependencies instanceof Schema) {
460
                            $dependencies->process($data, $options, $path . '->dependencies:' . $key);
461
                        } else {
462
                            foreach ($dependencies as $item) {
463
                                if (!property_exists($data, $item)) {
464
                                    $this->fail(new ObjectException('Dependency property missing: ' . $item,
465
                                        ObjectException::DEPENDENCY_MISSING), $path);
466
                                }
467
                            }
468
                        }
469
                    }
470
                }
471
472
                $propertyFound = false;
473
                if (isset($properties[$key])) {
474
                    $prop = $properties[$key];
475
                    $propertyFound = true;
476
                    $found = true;
477
                    $value = $prop->process($value, $options, $path . '->properties:' . $key);
478
                }
479
480
                /** @var Egg[] $nestedEggs */
481
                $nestedEggs = null;
482
                if (isset($nestedProperties[$key])) {
483
                    $found = true;
484
                    $nestedEggs = $nestedProperties[$key];
485
                    // todo iterate all nested props?
486
                    $value = $nestedEggs[0]->propertySchema->process($value, $options, $path . '->nestedProperties:' . $key);
487
                }
488
489
                if ($this->patternProperties !== null) {
490
                    foreach ($this->patternProperties as $pattern => $propertySchema) {
491
                        if (preg_match(Helper::toPregPattern($pattern), $key)) {
492
                            $found = true;
493
                            $value = $propertySchema->process($value, $options,
494
                                $path . '->patternProperties[' . $pattern . ']:' . $key);
495
                            if ($import) {
496
                                $result->addPatternPropertyName($pattern, $key);
497
                            }
498
                            //break; // todo manage multiple import data properly (pattern accessor)
499
                        }
500
                    }
501
                }
502
                if (!$found && $this->additionalProperties !== null) {
503
                    if (!$options->skipValidation && $this->additionalProperties === false) {
504
                        $this->fail(new ObjectException('Additional properties not allowed'), $path . ':' . $key);
505
                    }
506
507
                    if ($this->additionalProperties !== false) {
508
                        $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
509
                    }
510
511
                    if ($import && !$this->useObjectAsArray) {
512
                        $result->addAdditionalPropertyName($key);
513
                    }
514
                }
515
516
                if ($nestedEggs && $import) {
517
                    foreach ($nestedEggs as $nestedEgg) {
518
                        $result->setNestedProperty($key, $value, $nestedEgg);
519
                    }
520
                    if ($propertyFound) {
521
                        $result->$key = $value;
522
                    }
523
                } else {
524
                    if ($this->useObjectAsArray && $import) {
525
                        $result[$key] = $value;
526
                    } else {
527
                        if ($found || !$import) {
528
                            $result->$key = $value;
529
                        } elseif (!isset($result->$key)) {
530
                            $result->$key = $value;
531
                        }
532
                    }
533
                }
534
            }
535
536
        }
537
538
        if (is_array($data)) {
539
540
            if (!$options->skipValidation) {
541
                if ($this->minItems !== null && count($data) < $this->minItems) {
542
                    $this->fail(new ArrayException("Not enough items in array"), $path);
543
                }
544
545
                if ($this->maxItems !== null && count($data) > $this->maxItems) {
546
                    $this->fail(new ArrayException("Too many items in array"), $path);
547
                }
548
            }
549
550
            $pathItems = 'items';
551
            if ($this->items instanceof Schema) {
552
                $items = array();
553
                $additionalItems = $this->items;
554
            } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
555
                $items = array();
556
                $additionalItems = true;
557
            } else { // listed items
558
                $items = $this->items;
559
                $additionalItems = $this->additionalItems;
560
                $pathItems = 'additionalItems';
561
            }
562
563
            if ($items !== null || $additionalItems !== null) {
564
                $itemsLen = is_array($items) ? count($items) : 0;
565
                $index = 0;
566
                foreach ($result as $key => $value) {
567
                    if ($index < $itemsLen) {
568
                        $result[$key] = $items[$index]->process($value, $options, $path . '->items:' . $index);
569
                    } else {
570
                        if ($additionalItems instanceof Schema) {
571
                            $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
572
                                . '[' . $index . ']');
573
                        } elseif (!$options->skipValidation && $additionalItems === false) {
574
                            $this->fail(new ArrayException('Unexpected array item'), $path);
575
                        }
576
                    }
577
                    ++$index;
578
                }
579
            }
580
581
            if (!$options->skipValidation && $this->uniqueItems) {
582
                if (!UniqueItems::isValid($data)) {
583
                    $this->fail(new ArrayException('Array is not unique'), $path);
584
                }
585
            }
586
        }
587
588
        return $result;
589
    }
590
591
    /**
592
     * @param boolean $useObjectAsArray
593
     * @return Schema
594
     */
595
    public function setUseObjectAsArray($useObjectAsArray)
596
    {
597
        $this->useObjectAsArray = $useObjectAsArray;
598
        return $this;
599
    }
600
601
    private function fail(InvalidValue $exception, $path)
602
    {
603
        if ($path !== '#') {
604
            $exception->addPath($path);
605
        }
606
        throw $exception;
607
    }
608
609
    public static function integer()
610
    {
611
        $schema = new static();
612
        $schema->type = Type::INTEGER;
613
        return $schema;
614
    }
615
616
    public static function number()
617
    {
618
        $schema = new static();
619
        $schema->type = Type::NUMBER;
620
        return $schema;
621
    }
622
623
    public static function string()
624
    {
625
        $schema = new static();
626
        $schema->type = Type::STRING;
627
        return $schema;
628
    }
629
630
    public static function boolean()
631
    {
632
        $schema = new static();
633
        $schema->type = Type::BOOLEAN;
634
        return $schema;
635
    }
636
637
    public static function object()
638
    {
639
        $schema = new static();
640
        $schema->type = Type::OBJECT;
641
        return $schema;
642
    }
643
644
    public static function arr()
645
    {
646
        $schema = new static();
647
        $schema->type = Type::ARR;
648
        return $schema;
649
    }
650
651
    public static function null()
652
    {
653
        $schema = new static();
654
        $schema->type = Type::NULL;
655
        return $schema;
656
    }
657
658
659
    /**
660
     * @param Properties $properties
661
     * @return Schema
662
     */
663
    public function setProperties($properties)
664
    {
665
        $this->properties = $properties;
666
        return $this;
667
    }
668
669
    public function setProperty($name, Schema $schema)
670
    {
671
        if (null === $this->properties) {
672
            $this->properties = new Properties();
673
        }
674
        $this->properties->__set($name, $schema);
675
        return $this;
676
    }
677
678
    /** @var Meta[] */
679
    private $metaItems = array();
680
681
    public function meta(Meta $meta)
682
    {
683
        $this->metaItems[get_class($meta)] = $meta;
684
        return $this;
685
    }
686
687
    public function getMeta($className)
688
    {
689
        if (isset($this->metaItems[$className])) {
690
            return $this->metaItems[$className];
691
        }
692
        return null;
693
    }
694
695
    /**
696
     * @param Context $options
697
     * @return ObjectItem
698
     */
699
    public function makeObjectItem(Context $options = null)
700
    {
701
        if (null === $this->objectItemClass) {
702
            return new ObjectItem();
703
        } else {
704
            $className = $this->objectItemClass;
705
            if ($options !== null) {
706
                if (isset($options->objectItemClassMapping[$className])) {
707
                    $className = $options->objectItemClassMapping[$className];
708
                }
709
            }
710
            return new $className;
711
        }
712
    }
713
}
714