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

SchemaLoader   C

Complexity

Total Complexity 72

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 72
lcom 2
cbo 8
dl 0
loc 364
rs 5.5667
c 1
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setRemoteRefProvider() 0 5 1
A getRefProvider() 0 7 2
A readSchema() 0 4 1
A dumpSchema() 0 8 1
D dumpSchemaDeeper() 0 43 10
F readSchemaDeeper() 0 173 45
C resolveReference() 0 46 11
A create() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like SchemaLoader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaLoader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Swaggest\JsonSchema;
4
5
use PhpLang\ScopeExit;
6
use Swaggest\JsonSchema\Constraint\Properties;
7
use Swaggest\JsonSchema\Constraint\Ref;
8
use Swaggest\JsonSchema\Constraint\Type;
9
use Swaggest\JsonSchema\RemoteRef\BasicFetcher;
10
11
/**
12
 * Class SchemaLoader
13
 * @package Swaggest\JsonSchema
14
 * @deprecated
15
 */
16
class SchemaLoader
17
{
18
    const ID = 'id';
19
20
    const TYPE = 'type';
21
22
    const PROPERTIES = 'properties';
23
    const PATTERN_PROPERTIES = 'patternProperties';
24
    const ADDITIONAL_PROPERTIES = 'additionalProperties';
25
    const REQUIRED = 'required';
26
    const DEPENDENCIES = 'dependencies';
27
    const MIN_PROPERTIES = 'minProperties';
28
    const MAX_PROPERTIES = 'maxProperties';
29
30
    const REF = '$ref';
31
32
    const ITEMS = 'items';
33
    const ADDITIONAL_ITEMS = 'additionalItems';
34
    const UNIQUE_ITEMS = 'uniqueItems';
35
    const MIN_ITEMS = 'minItems';
36
    const MAX_ITEMS = 'maxItems';
37
38
    const ENUM = 'enum';
39
40
    const MINIMUM = 'minimum';
41
    const EXCLUSIVE_MINIMUM = 'exclusiveMinimum';
42
    const MAXIMUM = 'maximum';
43
    const EXCLUSIVE_MAXIMUM = 'exclusiveMaximum';
44
    const MULTIPLE_OF = 'multipleOf';
45
46
    const PATTERN = 'pattern';
47
    const MIN_LENGTH = 'minLength';
48
    const MAX_LENGTH = 'maxLength';
49
50
    const NOT = 'not';
51
    const ALL_OF = 'allOf';
52
    const ANY_OF = 'anyOf';
53
    const ONE_OF = 'oneOf';
54
55
    /** @var Schema */
56
    private $rootSchema;
57
58
    private $rootData;
59
60
    /** @var Ref[] */
61
    private $refs = array();
62
63
    /** @var SchemaLoader[] */
64
    private $remoteSchemaLoaders = array();
65
66
    /** @var RemoteRefProvider */
67
    private $refProvider;
68
69
    public function setRemoteRefProvider(RemoteRefProvider $provider)
70
    {
71
        $this->refProvider = $provider;
72
        return $this;
73
    }
74
75
    private function getRefProvider()
76
    {
77
        if (null === $this->refProvider) {
78
            $this->refProvider = new BasicFetcher();
79
        }
80
        return $this->refProvider;
81
    }
82
83
    public function readSchema($schemaData)
84
    {
85
        return $this->readSchemaDeeper($schemaData);
86
    }
87
88
    /** @var \SplObjectStorage */
89
    private $circularReferences;
90
    public function dumpSchema(Schema $schema)
91
    {
92
        $this->circularReferences = new \SplObjectStorage();
93
        $this->dumpDefinitions = array();
0 ignored issues
show
Bug introduced by
The property dumpDefinitions does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
94
        $this->dumpDefIndex = 0;
0 ignored issues
show
Bug introduced by
The property dumpDefIndex does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
95
        $contents = $this->dumpSchemaDeeper($schema, '#');
96
        return $contents;
97
    }
98
99
    private function dumpSchemaDeeper(Schema $schema, $path)
100
    {
101
        $result = new \stdClass();
102
103
        if ($this->circularReferences->contains($schema)) {
104
            $path = $this->circularReferences[$schema];
105
            $result->{self::REF} = $path;
106
            return $result;
107
        }
108
        $this->circularReferences->attach($schema, $path);
109
110
        $data = get_object_vars($schema);
111
        foreach ($data as $key => $value) {
112
            if ($value === null) {
113
                continue;
114
            }
115
116
            if ($value instanceof Schema) {
117
                $value = $this->dumpSchemaDeeper($value, $path . '/' . $key);
118
            }
119
120
            if (is_array($value)) {
121
                foreach ($value as $k => $v) {
122
                    if ($v instanceof Schema) {
123
                        $value[$k] = $this->dumpSchemaDeeper($v, $path . '/' . $key . '/' . $k);
124
                    }
125
                }
126
            }
127
128
            if ($key === self::PROPERTIES) {
129
                /** @var Properties $properties */
130
                $properties = $value;
131
                $value = array();
132
                foreach ($properties->toArray() as $propertyName => $property) {
133
                    $value[$propertyName] = $this->dumpSchemaDeeper($property, $path . '/' . $key . '/' . $propertyName);
134
                }
135
            }
136
137
138
            $result->$key = $value;
139
        }
140
        return $result;
141
    }
142
143
    private $resolutionScope;
144
145
    protected function readSchemaDeeper($schemaArray)
146
    {
147
        $schema = new Schema();
148
        if (null === $this->rootSchema) {
149
            $this->rootSchema = $schema;
150
            $this->rootData = $schemaArray;
151
        }
152
153
        if ($schemaArray instanceof \stdClass) {
154
            $schemaArray = (array)$schemaArray;
155
        }
156
157
        if (isset($schemaArray[self::ID])) {
158
            $parentScope = $this->resolutionScope;
159
            $this->resolutionScope = Helper::resolveURI($parentScope, $schemaArray[self::ID]);
160
            /** @noinspection PhpUnusedLocalVariableInspection */
161
            $defer = new ScopeExit(function () use ($parentScope) {
0 ignored issues
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...
162
                $this->resolutionScope = $parentScope;
163
            });
164
        }
165
166
        if (isset($schemaArray[self::TYPE])) {
167
            $schema->type = $schemaArray[self::TYPE];
168
        }
169
170
171
        // Object
172
        if (isset($schemaArray[self::PROPERTIES])) {
173
            $properties = new Properties();
174
            $schema->properties = $properties;
175
            foreach ($schemaArray[self::PROPERTIES] as $name => $data) {
176
                $properties->__set($name, $this->readSchemaDeeper($data));
177
            }
178
        }
179
180
        if (isset($schemaArray[self::PATTERN_PROPERTIES])) {
181
            foreach ($schemaArray[self::PATTERN_PROPERTIES] as $name => $data) {
182
                $schema->patternProperties[$name] = $this->readSchemaDeeper($data);
183
            }
184
        }
185
186
        if (isset($schemaArray[self::ADDITIONAL_PROPERTIES])) {
187
            $additionalProperties = $schemaArray[self::ADDITIONAL_PROPERTIES];
188
            if ($additionalProperties instanceof \stdClass) {
189
                $schema->additionalProperties = $this->readSchemaDeeper($additionalProperties);
190
            } elseif (is_bool($additionalProperties)) {
191
                $schema->additionalProperties = $additionalProperties;
192
            } else {
193
                throw new InvalidValue('Object or boolean required for additionalProperties', InvalidValue::INVALID_VALUE);
194
            }
195
        }
196
197
        if (isset($schemaArray[self::REQUIRED])) {
198
            $schema->required = $schemaArray[self::REQUIRED];
199
        }
200
201
        if (isset($schemaArray[self::DEPENDENCIES])) {
202
            foreach ($schemaArray[self::DEPENDENCIES] as $key => $value) {
203
                if ($value instanceof \stdClass) {
204
                    $schema->dependencies[$key] = $this->readSchemaDeeper($value);
205
                } else {
206
                    $schema->dependencies[$key] = $value;
207
                }
208
            }
209
        }
210
211
        if (isset($schemaArray[self::MIN_PROPERTIES])) {
212
            $schema->minProperties = $schemaArray[self::MIN_PROPERTIES];
213
        }
214
        if (isset($schemaArray[self::MAX_PROPERTIES])) {
215
            $schema->maxProperties = $schemaArray[self::MAX_PROPERTIES];
216
        }
217
218
219
        // Array
220
        if (isset($schemaArray[self::ITEMS])) {
221
            $items = $schemaArray[self::ITEMS];
222
            if (is_array($items)) {
223
                $schema->items = array();
224
                foreach ($items as $item) {
225
                    $schema->items[] = $this->readSchemaDeeper($item);
226
                }
227
            } elseif ($items instanceof \stdClass) {
228
                $schema->items = $this->readSchemaDeeper($items);
229
            }
230
        }
231
232
233
        if (isset($schemaArray[self::ADDITIONAL_ITEMS])) {
234
            $additionalItems = $schemaArray[self::ADDITIONAL_ITEMS];
235
            if ($additionalItems instanceof \stdClass) {
236
                $schema->additionalItems = $this->readSchemaDeeper($additionalItems);
237
            } else {
238
                $schema->additionalItems = $additionalItems;
239
            }
240
        }
241
242
        if (isset($schemaArray[self::UNIQUE_ITEMS]) && $schemaArray[self::UNIQUE_ITEMS] === true) {
243
            $schema->uniqueItems = true;
244
        }
245
246
        if (isset($schemaArray[self::MIN_ITEMS])) {
247
            $schema->minItems = $schemaArray[self::MIN_ITEMS];
248
        }
249
250
        if (isset($schemaArray[self::MAX_ITEMS])) {
251
            $schema->maxItems = $schemaArray[self::MAX_ITEMS];
252
        }
253
254
255
        // Number
256
        if (isset($schemaArray[self::MINIMUM])) {
257
            $schema->minimum = $schemaArray[self::MINIMUM];
258
        }
259
        if (isset($schemaArray[self::EXCLUSIVE_MINIMUM])) {
260
            $schema->exclusiveMinimum = $schemaArray[self::EXCLUSIVE_MINIMUM];
261
        }
262
        if (isset($schemaArray[self::MAXIMUM])) {
263
            $schema->maximum = $schemaArray[self::MAXIMUM];
264
        }
265
        if (isset($schemaArray[self::EXCLUSIVE_MAXIMUM])) {
266
            $schema->exclusiveMaximum = $schemaArray[self::EXCLUSIVE_MAXIMUM];
267
        }
268
        if (isset($schemaArray[self::MULTIPLE_OF])) {
269
            $schema->multipleOf = $schemaArray[self::MULTIPLE_OF];
270
        }
271
272
273
        // String
274
        if (isset($schemaArray[self::PATTERN])) {
275
            $schema->pattern = $schemaArray[self::PATTERN];
276
277
        }
278
        if (isset($schemaArray[self::MIN_LENGTH])) {
279
            $schema->minLength = $schemaArray[self::MIN_LENGTH];
280
        }
281
        if (isset($schemaArray[self::MAX_LENGTH])) {
282
            $schema->maxLength = $schemaArray[self::MAX_LENGTH];
283
        }
284
285
286
        // Misc
287
        if (isset($schemaArray[self::ENUM])) {
288
            $schema->enum = $schemaArray[self::ENUM];
289
        }
290
291
        // Logic
292
        if (isset($schemaArray[self::ALL_OF])) {
293
            foreach ($schemaArray[self::ALL_OF] as $item) {
294
                $schema->allOf[] = $this->readSchemaDeeper($item);
295
            }
296
        }
297
        if (isset($schemaArray[self::ANY_OF])) {
298
            foreach ($schemaArray[self::ANY_OF] as $item) {
299
                $schema->anyOf[] = $this->readSchemaDeeper($item);
300
            }
301
        }
302
        if (isset($schemaArray[self::ONE_OF])) {
303
            foreach ($schemaArray[self::ONE_OF] as $item) {
304
                $schema->oneOf[] = $this->readSchemaDeeper($item);
305
            }
306
        }
307
        if (isset($schemaArray[self::NOT])) {
308
            $schema->not = $this->readSchemaDeeper($schemaArray[self::NOT]);
309
        }
310
311
        // should resolve references on load
312
        if (isset($schemaArray[self::REF])) {
313
            $schema->ref = $this->resolveReference($schemaArray[self::REF]);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->resolveReference($schemaArray[self::REF]) of type object<Swaggest\JsonSchema\Constraint\Ref> is incompatible with the declared type string of property $ref.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
314
        }
315
316
        return $schema;
317
    }
318
319
320
    /**
321
     * @param $referencePath
322
     * @return Ref
323
     * @throws \Exception
324
     */
325
    private function resolveReference($referencePath)
326
    {
327
        $ref = &$this->refs[$referencePath];
328
        if (null === $ref) {
329
            if ($referencePath[0] === '#') {
330
                if ($referencePath === '#') {
331
                    $ref = new Ref($referencePath, $this->rootSchema);
332
                } else {
333
                    $ref = new Ref($referencePath);
334
                    $path = explode('/', trim($referencePath, '#/'));
335
                    $branch = &$this->rootData;
336
                    while (!empty($path)) {
337
                        $folder = array_shift($path);
338
339
                        // unescaping special characters
340
                        // https://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07#section-4
341
                        // https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/130
342
                        $folder = str_replace(array('~0', '~1', '%25'), array('~', '/', '%'), $folder);
343
344
                        if ($branch instanceof \stdClass && isset($branch->$folder)) {
345
                            $branch = &$branch->$folder;
346
                        } elseif (is_array($branch) && isset($branch[$folder])) {
347
                            $branch = &$branch[$folder];
348
                        } else {
349
                            throw new \Exception('Could not resolve ' . $referencePath . ': ' . $folder);
350
                        }
351
                    }
352
                    $ref->setData($this->readSchema($branch));
353
                }
354
            } else {
355
                $refParts = explode('#', $referencePath);
356
                $url = Helper::resolveURI($this->resolutionScope, $refParts[0]);
357
                $url = rtrim($url, '#');
358
                $refLocalPath = isset($refParts[1]) ? '#' . $refParts[1] : '#';
359
                $schemaLoader = &$this->remoteSchemaLoaders[$url];
360
                if (null === $schemaLoader) {
361
                    $schemaLoader = SchemaLoader::create();
362
                    $schemaLoader->readSchema($this->getRefProvider()->getSchemaData($url));
363
                }
364
365
                $ref = $schemaLoader->resolveReference($refLocalPath);
366
            }
367
        }
368
369
        return $this->refs[$referencePath];
370
    }
371
372
    /**
373
     * @return static
374
     */
375
    public static function create()
376
    {
377
        return new static;
0 ignored issues
show
Deprecated Code introduced by
The class Swaggest\JsonSchema\SchemaLoader has been deprecated.

This class, trait or interface has been deprecated.

Loading history...
378
    }
379
}
380