Completed
Pull Request — master (#9)
by Viacheslav
39:48
created

Schema::preProcessReferences()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 7.551
c 0
b 0
f 0
cc 7
eloc 11
nc 7
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 ObjectItem
26
{
27
    const SCHEMA_DRAFT_04_URL = 'http://json-schema.org/draft-04/schema';
28
29
    /*
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...
30
    public $__seqId;
31
    public static $seq = 0;
32
33
    public function __construct()
34
    {
35
        self::$seq++;
36
        $this->__seqId = self::$seq;
37
    }
38
    //*/
39
40
    /** @var Type */
41
    public $type;
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[] */
51
    public $required;
52
    /** @var string[][]|Schema[] */
53
    public $dependencies;
54
    /** @var int */
55
    public $minProperties;
56
    /** @var int */
57
    public $maxProperties;
58
59
    // Array
60
    /** @var Schema|Schema[] */
61
    public $items;
62
    /** @var Schema|bool */
63
    public $additionalItems;
64
    /** @var bool */
65
    public $uniqueItems;
66
    /** @var int */
67
    public $minItems;
68
    /** @var int */
69
    public $maxItems;
70
71
    // Reference
72
    /** @var string */
73
    public $ref;
74
75
    // Enum
76
    /** @var array */
77
    public $enum;
78
79
    // Number
80
    /** @var int */
81
    public $maximum;
82
    /** @var bool */
83
    public $exclusiveMaximum;
84
    /** @var int */
85
    public $minimum;
86
    /** @var bool */
87
    public $exclusiveMinimum;
88
    /** @var float|int */
89
    public $multipleOf;
90
91
92
    // String
93
    /** @var string */
94
    public $pattern;
95
    /** @var int */
96
    public $minLength;
97
    /** @var int */
98
    public $maxLength;
99
    /** @var string */
100
    public $format;
101
102
    const FORMAT_DATE_TIME = 'date-time'; // todo implement
103
104
105
    /** @var Schema[] */
106
    public $allOf;
107
    /** @var Schema */
108
    public $not;
109
    /** @var Schema[] */
110
    public $anyOf;
111
    /** @var Schema[] */
112
    public $oneOf;
113
114
    public $objectItemClass;
115
    private $useObjectAsArray = false;
116
117
    private $__dataToProperty = array();
118
    private $__propertyToData = array();
119
120
121
    public function addPropertyMapping($dataName, $propertyName)
122
    {
123
        $this->__dataToProperty[$dataName] = $propertyName;
124
        $this->__propertyToData[$propertyName] = $dataName;
125
        return $this;
126
    }
127
128
    private function preProcessReferences($data, ProcessingOptions $options = null)
129
    {
130
        if (is_array($data)) {
131
            foreach ($data as $key => $item) {
132
                $this->preProcessReferences($item, $options);
133
            }
134
        } elseif ($data instanceof \stdClass) {
135
            /** @var JsonSchema $data */
136
            if (isset($data->id) && is_string($data->id)) {
137
                $prev = $options->refResolver->setupResolutionScope($data->id, $data);
138
                /** @noinspection PhpUnusedLocalVariableInspection */
139
                $_ = 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...
140
                    $options->refResolver->setResolutionScope($prev);
141
                });
142
            }
143
144
            foreach ((array)$data as $key => $value) {
145
                $this->preProcessReferences($value, $options);
146
            }
147
        }
148
    }
149
150
    public function import($data, ProcessingOptions $options = null)
151
    {
152
        if ($options === null) {
153
            $options = new ProcessingOptions();
154
        }
155
156
        $options->import = true;
0 ignored issues
show
Documentation introduced by
The property $import is declared protected in Swaggest\JsonSchema\ProcessingOptions. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
157
158
        $options->refResolver = new RefResolver($data);
159
        if ($options->remoteRefProvider) {
160
            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
161
        }
162
163
        if ($options->import) {
0 ignored issues
show
Documentation introduced by
The property $import is declared protected in Swaggest\JsonSchema\ProcessingOptions. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
164
            $this->preProcessReferences($data, $options);
165
        }
166
167
        return $this->process($data, $options, '#');
168
    }
169
170
    public function export($data, ProcessingOptions $options = null)
171
    {
172
        if ($options === null) {
173
            $options = new ProcessingOptions();
174
        }
175
176
        $options->import = false;
0 ignored issues
show
Documentation introduced by
The property $import is declared protected in Swaggest\JsonSchema\ProcessingOptions. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
177
        return $this->process($data, $options);
178
    }
179
180
    public function process($data, ProcessingOptions $options, $path = '#', $result = null)
181
    {
182
183
        $import = $options->import;
0 ignored issues
show
Documentation introduced by
The property $import is declared protected in Swaggest\JsonSchema\ProcessingOptions. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
184
        //$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...
185
186
        if (!$import && $data instanceof ObjectItem) {
187
            $data = $data->jsonSerialize();
188
        }
189
        if (!$import && is_array($data) && $this->useObjectAsArray) {
190
            $data = (object)$data;
191
        }
192
193
        if (null !== $options->dataPreProcessor) {
194
            $data = $options->dataPreProcessor->process($data, $this, $import);
195
        }
196
197
        if ($result === null) {
198
            $result = $data;
199
        }
200
201
        if ($this->type !== null) {
202
            if (!Type::isValid($this->type, $data)) {
203
                $this->fail(new TypeException(ucfirst(
204
                        implode(', ', is_array($this->type) ? $this->type : array($this->type))
205
                        . ' expected, ' . json_encode($data) . ' received')
206
                ), $path);
207
            }
208
        }
209
210
        if ($this->enum !== null) {
211
            $enumOk = false;
212
            foreach ($this->enum as $item) {
213
                if ($item === $data) { // todo support complex structures here
214
                    $enumOk = true;
215
                    break;
216
                }
217
            }
218
            if (!$enumOk) {
219
                $this->fail(new EnumException('Enum failed'), $path);
220
            }
221
        }
222
223
        if ($this->not !== null) {
224
            $exception = false;
225
            try {
226
                $this->not->process($data, $options, $path . '->not');
227
            } catch (InvalidValue $exception) {
228
                // Expected exception
229
            }
230
            if ($exception === false) {
231
                $this->fail(new LogicException('Failed due to logical constraint: not'), $path);
232
            }
233
        }
234
235
        if ($this->oneOf !== null) {
236
            $successes = 0;
237
            $failures = '';
238
            foreach ($this->oneOf as $index => $item) {
239
                try {
240
                    $result = $item->process($data, $options, $path . '->oneOf:' . $index);
241
                    $successes++;
242
                    if ($successes > 1) {
243
                        break;
244
                    }
245
                } catch (InvalidValue $exception) {
246
                    $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
247
                    // Expected exception
248
                }
249
            }
250
            if ($successes === 0) {
251
                $this->fail(new LogicException('Failed due to logical constraint: no valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}"), $path);
252
            } elseif ($successes > 1) {
253
                $this->fail(new LogicException('Failed due to logical constraint: '
254
                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf'), $path);
255
            }
256
        }
257
258
        if ($this->anyOf !== null) {
259
            $successes = 0;
260
            $failures = '';
261
            foreach ($this->anyOf as $index => $item) {
262
                try {
263
                    $result = $item->process($data, $options, $path . '->anyOf:' . $index);
264
                    $successes++;
265
                    if ($successes) {
266
                        break;
267
                    }
268
                } catch (InvalidValue $exception) {
269
                    $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
270
                    // Expected exception
271
                }
272
            }
273
            if (!$successes) {
274
                $this->fail(new LogicException('Failed due to logical constraint: no valid results for anyOf {' . "\n" . substr($failures, 0, -1) . "\n}"), $path);
275
            }
276
        }
277
278
        if ($this->allOf !== null) {
279
            foreach ($this->allOf as $index => $item) {
280
                $result = $item->process($data, $options, $path . '->allOf' . $index);
281
            }
282
        }
283
284
285
        if (is_string($data)) {
286
            if ($this->minLength !== null) {
287
                if (mb_strlen($data, 'UTF-8') < $this->minLength) {
288
                    $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
289
                }
290
            }
291
            if ($this->maxLength !== null) {
292
                if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
293
                    $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
294
                }
295
            }
296
            if ($this->pattern !== null) {
297
                if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
298
                    $this->fail(new StringException(json_encode($data) . ' does not match to '
299
                        . $this->pattern, StringException::PATTERN_MISMATCH), $path);
300
                }
301
            }
302
        }
303
304
        if (is_int($data) || is_float($data)) {
305
            if ($this->multipleOf !== null) {
306
                $div = $data / $this->multipleOf;
307
                if ($div != (int)$div) {
308
                    $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
309
                }
310
            }
311
312
            if ($this->maximum !== null) {
313
                if ($this->exclusiveMaximum === true) {
314
                    if ($data >= $this->maximum) {
315
                        $this->fail(new NumericException(
316
                            'Value less or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
317
                            NumericException::MAXIMUM), $path);
318
                    }
319
                } else {
320
                    if ($data > $this->maximum) {
321
                        $this->fail(new NumericException(
322
                            'Value less than ' . $this->minimum . ' expected, ' . $data . ' received',
323
                            NumericException::MAXIMUM), $path);
324
                    }
325
                }
326
            }
327
328
            if ($this->minimum !== null) {
329
                if ($this->exclusiveMinimum === true) {
330
                    if ($data <= $this->minimum) {
331
                        $this->fail(new NumericException(
332
                            'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
333
                            NumericException::MINIMUM), $path);
334
                    }
335
                } else {
336
                    if ($data < $this->minimum) {
337
                        $this->fail(new NumericException(
338
                            'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
339
                            NumericException::MINIMUM), $path);
340
                    }
341
                }
342
            }
343
        }
344
345
346
        if ($data instanceof \stdClass) {
347
            if ($this->required !== null) {
348
                foreach ($this->required as $item) {
349
                    if (!property_exists($data, $item)) {
350
                        $this->fail(new ObjectException('Required property missing: ' . $item, ObjectException::REQUIRED), $path);
351
                    }
352
                }
353
            }
354
355
            if ($import) {
356
                if ($this->useObjectAsArray) {
357
                    $result = array();
358
                } elseif (!$result instanceof ObjectItem) {
359
                    $result = $this->makeObjectItem();
360
361
                    if ($result instanceof ClassStructure) {
362
                        if ($result->__validateOnSet) {
0 ignored issues
show
Documentation introduced by
The property $__validateOnSet is declared protected in Swaggest\JsonSchema\Structure\ClassStructure. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
363
                            $result->__validateOnSet = false;
0 ignored issues
show
Documentation introduced by
The property $__validateOnSet is declared protected in Swaggest\JsonSchema\Structure\ClassStructure. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
364
                            /** @noinspection PhpUnusedLocalVariableInspection */
365
                            $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...
366
                                $result->__validateOnSet = true;
0 ignored issues
show
Documentation introduced by
The property $__validateOnSet is declared protected in Swaggest\JsonSchema\Structure\ClassStructure. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

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