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