Completed
Push — master ( 470314...0dded8 )
by Viacheslav
02:56
created

Schema::fail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 2
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 ($this->type !== null) {
171
            if (!Type::isValid($this->type, $data)) {
172
                $this->fail(new TypeException(ucfirst(
173
                        implode(', ', is_array($this->type) ? $this->type : array($this->type))
174
                        . ' expected, ' . json_encode($data) . ' received')
175
                ), $path);
176
            }
177
        }
178
179
        if ($this->enum !== null) {
180
            $enumOk = false;
181
            foreach ($this->enum as $item) {
182
                if ($item === $data) { // todo support complex structures here
183
                    $enumOk = true;
184
                    break;
185
                }
186
            }
187
            if (!$enumOk) {
188
                $this->fail(new EnumException('Enum failed'), $path);
189
            }
190
        }
191
192
        if ($this->not !== null) {
193
            $exception = false;
194
            try {
195
                $this->not->process($data, $options, $path . '->not');
196
            } catch (InvalidValue $exception) {
197
                // Expected exception
198
            }
199
            if ($exception === false) {
200
                $this->fail(new LogicException('Failed due to logical constraint: not'), $path);
201
            }
202
        }
203
204
        if ($this->oneOf !== null) {
205
            $successes = 0;
206
            $failures = '';
207
            foreach ($this->oneOf as $index => $item) {
208
                try {
209
                    $result = $item->process($data, $options, $path . '->oneOf:' . $index);
210
                    $successes++;
211
                    if ($successes > 1) {
212
                        break;
213
                    }
214
                } catch (InvalidValue $exception) {
215
                    $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
216
                    // Expected exception
217
                }
218
            }
219
            if ($successes === 0) {
220
                $this->fail(new LogicException('Failed due to logical constraint: no valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}"), $path);
221
            } elseif ($successes > 1) {
222
                $this->fail(new LogicException('Failed due to logical constraint: '
223
                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf'), $path);
224
            }
225
        }
226
227
        if ($this->anyOf !== null) {
228
            $successes = 0;
229
            $failures = '';
230
            foreach ($this->anyOf as $index => $item) {
231
                try {
232
                    $result = $item->process($data, $options, $path . '->anyOf:' . $index);
233
                    $successes++;
234
                    if ($successes) {
235
                        break;
236
                    }
237
                } catch (InvalidValue $exception) {
238
                    $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
239
                    // Expected exception
240
                }
241
            }
242
            if (!$successes) {
243
                $this->fail(new LogicException('Failed due to logical constraint: no valid results for anyOf {' . "\n" . substr($failures, 0, -1) . "\n}"), $path);
244
            }
245
        }
246
247
        if ($this->allOf !== null) {
248
            foreach ($this->allOf as $index => $item) {
249
                $result = $item->process($data, $options, $path . '->allOf' . $index);
250
            }
251
        }
252
253
254
        if (is_string($data)) {
255
            if ($this->minLength !== null) {
256
                if (mb_strlen($data, 'UTF-8') < $this->minLength) {
257
                    $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
258
                }
259
            }
260
            if ($this->maxLength !== null) {
261
                if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
262
                    $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
263
                }
264
            }
265
            if ($this->pattern !== null) {
266
                if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
267
                    $this->fail(new StringException(json_encode($data) . ' does not match to '
268
                        . $this->pattern, StringException::PATTERN_MISMATCH), $path);
269
                }
270
            }
271
        }
272
273
        if (is_int($data) || is_float($data)) {
274
            if ($this->multipleOf !== null) {
275
                $div = $data / $this->multipleOf;
276
                if ($div != (int)$div) {
277
                    $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
278
                }
279
            }
280
281
            if ($this->maximum !== null) {
282
                if ($this->exclusiveMaximum === true) {
283
                    if ($data >= $this->maximum) {
284
                        $this->fail(new NumericException(
285
                            'Value less or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
286
                            NumericException::MAXIMUM), $path);
287
                    }
288
                } else {
289
                    if ($data > $this->maximum) {
290
                        $this->fail(new NumericException(
291
                            'Value less than ' . $this->minimum . ' expected, ' . $data . ' received',
292
                            NumericException::MAXIMUM), $path);
293
                    }
294
                }
295
            }
296
297
            if ($this->minimum !== null) {
298
                if ($this->exclusiveMinimum === true) {
299
                    if ($data <= $this->minimum) {
300
                        $this->fail(new NumericException(
301
                            'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
302
                            NumericException::MINIMUM), $path);
303
                    }
304
                } else {
305
                    if ($data < $this->minimum) {
306
                        $this->fail(new NumericException(
307
                            'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
308
                            NumericException::MINIMUM), $path);
309
                    }
310
                }
311
            }
312
        }
313
314
315
        if ($data instanceof \stdClass) {
316
            if ($this->required !== null) {
317
                foreach ($this->required as $item) {
318
                    if (!property_exists($data, $item)) {
319
                        $this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path);
320
                    }
321
                }
322
            }
323
324
            if ($import) {
325
                if ($this->useObjectAsArray) {
326
                    $result = array();
327
                } elseif (!$result instanceof ObjectItem) {
328
                    $result = $this->makeObjectItem();
329
330
                    if ($result instanceof ClassStructure) {
331
                        if ($result->__validateOnSet) {
332
                            $result->__validateOnSet = false;
333
                            /** @noinspection PhpUnusedLocalVariableInspection */
334
                            $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...
335
                                $result->__validateOnSet = true;
336
                            });
337
                        }
338
                    }
339
340
                    if ($result instanceof ObjectItem) {
341
                        $result->__documentPath = $path;
342
                    }
343
                }
344
            }
345
346
            if ($import) {
347
                try {
348
                    while (
349
                        isset($data->{self::REF})
350
                        && is_string($data->{self::REF})
351
                        && !isset($this->properties[self::REF])
352
                    ) {
353
                        $refString = $data->{self::REF};
354
                        // TODO consider process # by reference here ?
355
                        $preRefScope = $options->refResolver->getResolutionScope();
356
                        /** @noinspection PhpUnusedLocalVariableInspection */
357
                        $deferRefScope = new ScopeExit(function () use ($preRefScope, $options) {
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...
358
                            $options->refResolver->setResolutionScope($preRefScope);
359
                        });
360
                        $ref = $options->refResolver->resolveReference($refString);
361
                        if ($ref->isImported()) {
362
                            $refResult = $ref->getImported();
363
                            return $refResult;
364
                        }
365
                        $data = $ref->getData();
366
                        if ($result instanceof ObjectItem) {
367
                            $result->__fromRef = $refString;
368
                        }
369
                        $ref->setImported($result);
370
                        $refResult = $this->process($data, $options, $path . '->ref:' . $refString, $result);
371
                        $ref->setImported($refResult);
372
                        return $refResult;
373
                    }
374
                } catch (InvalidValue $exception) {
375
                    $this->fail($exception, $path);
376
                }
377
            }
378
379
            // @todo better check for schema id
380
381
            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...
382
                $id = $data->id;
383
                $refResolver = $options->refResolver;
384
                $parentScope = $refResolver->updateResolutionScope($id);
385
                /** @noinspection PhpUnusedLocalVariableInspection */
386
                $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...
387
                    $refResolver->setResolutionScope($parentScope);
388
                });
389
            }
390
391
            if ($this->properties !== null) {
392
                /** @var Schema[] $properties */
393
                $properties = &$this->properties->toArray(); // TODO check performance of pointer
394
                if ($this->properties instanceof Properties) {
395
                    $nestedProperties = $this->properties->getNestedProperties();
396
                } else {
397
                    $nestedProperties = array();
398
                }
399
            }
400
401
            $array = array();
402
            if (!empty($this->__dataToProperty)) {
403
                foreach ((array)$data as $key => $value) {
404
                    if ($import) {
405
                        if (isset($this->__dataToProperty[$key])) {
406
                            $key = $this->__dataToProperty[$key];
407
                        }
408
                    } else {
409
                        if (isset($this->__propertyToData[$key])) {
410
                            $key = $this->__propertyToData[$key];
411
                        }
412
                    }
413
                    $array[$key] = $value;
414
                }
415
            } else {
416
                $array = (array)$data;
417
            }
418
419
            if ($this->minProperties !== null && count($array) < $this->minProperties) {
420
                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
421
            }
422
            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
423
                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
424
            }
425
            foreach ($array as $key => $value) {
426
                if ($key === '' && PHP_VERSION_ID < 71000) {
427
                    $this->fail(new InvalidValue('Empty property name'), $path);
428
                }
429
430
                $found = false;
431
                if (isset($this->dependencies->$key)) {
432
                    $dependencies = $this->dependencies->$key;
433
                    if ($dependencies instanceof Schema) {
434
                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
435
                    } else {
436
                        foreach ($dependencies as $item) {
437
                            if (!property_exists($data, $item)) {
438
                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
439
                                    ObjectException::DEPENDENCY_MISSING), $path);
440
                            }
441
                        }
442
                    }
443
                }
444
445
                $propertyFound = false;
446
                if (isset($properties[$key])) {
447
                    $prop = $properties[$key];
448
                    $propertyFound = true;
449
                    $found = true;
450
                    $value = $prop->process($value, $options, $path . '->properties:' . $key);
451
                }
452
453
                /** @var Egg[] $nestedEggs */
454
                $nestedEggs = null;
455
                if (isset($nestedProperties[$key])) {
456
                    $found = true;
457
                    $nestedEggs = $nestedProperties[$key];
458
                    // todo iterate all nested props?
459
                    $value = $nestedEggs[0]->propertySchema->process($value, $options, $path . '->nestedProperties:' . $key);
460
                }
461
462
                if ($this->patternProperties !== null) {
463
                    foreach ($this->patternProperties as $pattern => $propertySchema) {
464
                        if (preg_match(Helper::toPregPattern($pattern), $key)) {
465
                            $found = true;
466
                            $value = $propertySchema->process($value, $options,
467
                                $path . '->patternProperties[' . $pattern . ']:' . $key);
468
                            if ($import) {
469
                                $result->addPatternPropertyName($pattern, $key);
470
                            }
471
                            //break; // todo manage multiple import data properly (pattern accessor)
472
                        }
473
                    }
474
                }
475
                if (!$found && $this->additionalProperties !== null) {
476
                    if ($this->additionalProperties === false) {
477
                        $this->fail(new ObjectException('Additional properties not allowed'), $path . ':' . $key);
478
                    }
479
480
                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
481
                    if ($import && !$this->useObjectAsArray) {
482
                        $result->addAdditionalPropertyName($key);
483
                    }
484
                }
485
486
                if ($nestedEggs && $import) {
487
                    foreach ($nestedEggs as $nestedEgg) {
488
                        $result->setNestedProperty($key, $value, $nestedEgg);
489
                    }
490
                    if ($propertyFound) {
491
                        $result->$key = $value;
492
                    }
493
                } else {
494
                    if ($this->useObjectAsArray && $import) {
495
                        $result[$key] = $value;
496
                    } else {
497
                        if ($found || !$import) {
498
                            $result->$key = $value;
499
                        } elseif (!isset($result->$key)) {
500
                            $result->$key = $value;
501
                        }
502
                    }
503
                }
504
            }
505
506
        }
507
508
        if (is_array($data)) {
509
510
            if ($this->minItems !== null && count($data) < $this->minItems) {
511
                $this->fail(new ArrayException("Not enough items in array"), $path);
512
            }
513
514
            if ($this->maxItems !== null && count($data) > $this->maxItems) {
515
                $this->fail(new ArrayException("Too many items in array"), $path);
516
            }
517
518
            $pathItems = 'items';
519
            if ($this->items instanceof Schema) {
520
                $items = array();
521
                $additionalItems = $this->items;
522
            } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
523
                $items = array();
524
                $additionalItems = true;
525
            } else { // listed items
526
                $items = $this->items;
527
                $additionalItems = $this->additionalItems;
528
                $pathItems = 'additionalItems';
529
            }
530
531
            if ($items !== null || $additionalItems !== null) {
532
                $itemsLen = is_array($items) ? count($items) : 0;
533
                $index = 0;
534
                foreach ($result as $key => $value) {
535
                    if ($index < $itemsLen) {
536
                        $result[$key] = $items[$index]->process($value, $options, $path . '->items:' . $index);
537
                    } else {
538
                        if ($additionalItems instanceof Schema) {
539
                            $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
540
                                . '[' . $index . ']');
541
                        } elseif ($additionalItems === false) {
542
                            $this->fail(new ArrayException('Unexpected array item'), $path);
543
                        }
544
                    }
545
                    ++$index;
546
                }
547
            }
548
549
            if ($this->uniqueItems) {
550
                if (!UniqueItems::isValid($data)) {
551
                    $this->fail(new ArrayException('Array is not unique'), $path);
552
                }
553
            }
554
        }
555
556
        return $result;
557
    }
558
559
    /**
560
     * @param boolean $useObjectAsArray
561
     * @return Schema
562
     */
563
    public function setUseObjectAsArray($useObjectAsArray)
564
    {
565
        $this->useObjectAsArray = $useObjectAsArray;
566
        return $this;
567
    }
568
569
    /**
570
     * @param bool|Schema $additionalProperties
571
     * @return Schema
572
     */
573
    public function setAdditionalProperties($additionalProperties)
574
    {
575
        $this->additionalProperties = $additionalProperties;
576
        return $this;
577
    }
578
579
    /**
580
     * @param Schema|Schema[] $items
581
     * @return Schema
582
     */
583
    public function setItems($items)
584
    {
585
        $this->items = $items;
586
        return $this;
587
    }
588
589
590
    private function fail(InvalidValue $exception, $path)
591
    {
592
        if ($path !== '#') {
593
            $exception->addPath($path);
594
        }
595
        throw $exception;
596
    }
597
598
    public static function integer()
599
    {
600
        $schema = new static();
601
        $schema->type = Type::INTEGER;
602
        return $schema;
603
    }
604
605
    public static function number()
606
    {
607
        $schema = new static();
608
        $schema->type = Type::NUMBER;
609
        return $schema;
610
    }
611
612
    public static function string()
613
    {
614
        $schema = new static();
615
        $schema->type = Type::STRING;
616
        return $schema;
617
    }
618
619
    public static function boolean()
620
    {
621
        $schema = new static();
622
        $schema->type = Type::BOOLEAN;
623
        return $schema;
624
    }
625
626
    public static function object()
627
    {
628
        $schema = new static();
629
        $schema->type = Type::OBJECT;
630
        return $schema;
631
    }
632
633
    public static function arr()
634
    {
635
        $schema = new static();
636
        $schema->type = Type::ARR;
637
        return $schema;
638
    }
639
640
    public static function null()
641
    {
642
        $schema = new static();
643
        $schema->type = Type::NULL;
644
        return $schema;
645
    }
646
647
648
    public static function create()
649
    {
650
        $schema = new static();
651
        return $schema;
652
    }
653
654
655
    /**
656
     * @param Properties $properties
657
     * @return Schema
658
     */
659
    public function setProperties($properties)
660
    {
661
        $this->properties = $properties;
662
        return $this;
663
    }
664
665
    public function setProperty($name, Schema $schema)
666
    {
667
        if (null === $this->properties) {
668
            $this->properties = new Properties();
669
        }
670
        $this->properties->__set($name, $schema);
671
        return $this;
672
    }
673
674
    /** @var Meta[] */
675
    private $metaItems = array();
676
677
    public function meta(Meta $meta)
678
    {
679
        $this->metaItems[get_class($meta)] = $meta;
680
        return $this;
681
    }
682
683
    public function getMeta($className)
684
    {
685
        if (isset($this->metaItems[$className])) {
686
            return $this->metaItems[$className];
687
        }
688
        return null;
689
    }
690
691
    /**
692
     * @return ObjectItem
693
     */
694
    public function makeObjectItem()
695
    {
696
        if (null === $this->objectItemClass) {
697
            return new ObjectItem();
698
        } else {
699
            return new $this->objectItemClass;
700
        }
701
    }
702
}
703