Completed
Push — master ( 0e6e64...9192b1 )
by Viacheslav
12s
created

SchemaBuilder   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 439
Duplicated Lines 0 %

Test Coverage

Coverage 92.62%

Importance

Changes 0
Metric Value
eloc 249
dl 0
loc 439
ccs 226
cts 244
cp 0.9262
rs 2.32
c 0
b 0
f 0
wmc 76

15 Methods

Rating   Name   Duplication   Size   Complexity  
B processObject() 0 47 8
A setSaveEnumConstInClass() 0 4 1
A setSkipProperties() 0 4 1
A processNamedClass() 0 21 4
B processEnum() 0 27 7
A skipProperty() 0 4 1
D processType() 0 58 18
A build() 0 21 3
A __construct() 0 7 1
B processArray() 0 34 8
B processOther() 0 48 8
A processLogic() 0 23 5
A copyTo() 0 5 1
A processFromRef() 0 14 5
A processRef() 0 22 5

How to fix   Complexity   

Complex Class

Complex classes like SchemaBuilder 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.

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 SchemaBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Swaggest\PhpCodeBuilder\JsonSchema;
4
5
6
use Swaggest\CodeBuilder\PlaceholderString;
7
use Swaggest\JsonSchema\Constraint\Type;
8
use Swaggest\JsonSchema\Schema;
9
use Swaggest\JsonSchema\Structure\ClassStructure;
10
use Swaggest\JsonSchema\Structure\ObjectItem;
11
use Swaggest\PhpCodeBuilder\PhpClass;
12
use Swaggest\PhpCodeBuilder\PhpCode;
13
use Swaggest\PhpCodeBuilder\PhpConstant;
14
use Swaggest\PhpCodeBuilder\Types\ReferenceTypeOf;
15
use Swaggest\PhpCodeBuilder\Types\TypeOf;
16
17
class SchemaBuilder
18
{
19
    /** @var Schema */
20
    private $schema;
21
    /** @var string */
22
    private $varName;
23
    /** @var bool */
24
    private $createVarName;
25
26
    /** @var PhpBuilder */
27
    private $phpBuilder;
28
    /** @var string */
29
    private $path;
30
31
    /** @var PhpCode */
32
    private $result;
33
34
    /** @var bool */
35
    private $skipProperties;
36
37
    /** @var PhpClass */
38
    private $saveEnumConstInClass;
39
40
    /**
41
     * SchemaBuilder constructor.
42
     * @param Schema $schema
43
     * @param string $varName
44
     * @param string $path
45
     * @param PhpBuilder $phpBuilder
46
     * @param bool $createVarName
47
     */
48 10
    public function __construct($schema, $varName, $path, PhpBuilder $phpBuilder, $createVarName = true)
49
    {
50 10
        $this->schema = $schema;
51 10
        $this->varName = $varName;
52 10
        $this->phpBuilder = $phpBuilder;
53 10
        $this->path = $path;
54 10
        $this->createVarName = $createVarName;
55 10
    }
56
57 10
    private function processType()
58
    {
59 10
        if ($this->schema->type !== null) {
60 6
            switch ($this->schema->type) {
61
                case Type::INTEGER:
62 4
                    $result = $this->createVarName
63 3
                        ? "{$this->varName} = ::schema::integer();"
64 4
                        : "{$this->varName}->type = ::schema::INTEGER;";
65 4
                    break;
66
67
                case Type::NUMBER:
68 5
                    $result = $this->createVarName
69 4
                        ? "{$this->varName} = ::schema::number();"
70 5
                        : "{$this->varName}->type = ::schema::NUMBER;";
71 5
                    break;
72
73
                case Type::BOOLEAN:
74 5
                    $result = $this->createVarName
75 5
                        ? "{$this->varName} = ::schema::boolean();"
76 5
                        : "{$this->varName}->type = ::schema::BOOLEAN;";
77 5
                    break;
78
79
                case Type::STRING:
80 5
                    $result = $this->createVarName
81 5
                        ? "{$this->varName} = ::schema::string();"
82 5
                        : "{$this->varName}->type = ::schema::STRING;";
83 5
                    break;
84
85
                case Type::ARR:
86 3
                    $result = $this->createVarName
87 3
                        ? "{$this->varName} = ::schema::arr();"
88 3
                        : "{$this->varName}->type = ::schema::_ARRAY;";
89 3
                    break;
90
91
                case Type::OBJECT:
92 6
                    return;
93
94
                case Type::NULL:
95 1
                    $result = $this->createVarName
96 1
                        ? "{$this->varName} = ::schema::null();"
97 1
                        : "{$this->varName}->type = ::schema::NULL;";
98 1
                    break;
99
100
                default:
101
                    $types = PhpCode::varExport($this->schema->type);
102
                    $result = $this->createVarName
103
                        ? "{$this->varName} = (new ::schema())->setType($types);"
104 6
                        : "{$this->varName}->type = $types;";
105
            }
106
        } else {
107 7
            if ($this->createVarName) {
108 7
                $result = "{$this->varName} = new ::schema();";
109
            }
110
        }
111
112 10
        if (isset($result)) {
113 10
            $this->result->addSnippet(
114 10
                new PlaceholderString($result . "\n", array('::schema' => new ReferenceTypeOf(Palette::schemaClass())))
0 ignored issues
show
Deprecated Code introduced by
The class Swaggest\PhpCodeBuilder\Types\ReferenceTypeOf has been deprecated: redundant by TypeOf ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

114
                new PlaceholderString($result . "\n", array('::schema' => /** @scrutinizer ignore-deprecated */ new ReferenceTypeOf(Palette::schemaClass())))
Loading history...
115
            );
116
        }
117 10
    }
118
119 10
    private function processNamedClass()
120
    {
121 10
        if (!$this->skipProperties
122
            //&& $this->schema->type === Type::OBJECT
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% 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...
123 10
            && $this->schema->properties !== null
124
        ) {
125 7
            $class = $this->phpBuilder->getClass($this->schema, $this->path);
126 7
            if ($this->schema->id === 'http://json-schema.org/draft-04/schema#') {
127
                $this->result->addSnippet(
128
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
129
                        array('::class' => new TypeOf(Palette::schemaClass())))
130
                );
131
            } else {
132 7
                $this->result->addSnippet(
133 7
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
134 7
                        array('::class' => new TypeOf($class)))
135
                );
136
            }
137 7
            return true;
138
        }
139 10
        return false;
140
    }
141
142 10
    private function processRef()
143
    {
144 10
        if (!$this->skipProperties
145
            //&& $this->schema->type === Type::OBJECT
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% 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...
146 10
            && !$this->phpBuilder->minimizeRefs
147 10
            && $this->schema->getFromRefs()
148
        ) {
149 1
            $class = $this->phpBuilder->getClass($this->schema, $this->path);
150 1
            if ($this->schema->id === 'http://json-schema.org/draft-04/schema#') {
151
                $this->result->addSnippet(
152
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
153
                        array('::class' => new TypeOf(Palette::schemaClass())))
154
                );
155
            } else {
156 1
                $this->result->addSnippet(
157 1
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
158 1
                        array('::class' => new TypeOf($class)))
159
                );
160
            }
161 1
            return true;
162
        }
163 10
        return false;
164
    }
165
166
167
    /**
168
     * @throws Exception
169
     */
170 10
    private function processObject()
171
    {
172 10
        if ($this->schema->type === Type::OBJECT) {
173 6
            if (!$this->skipProperties) {
174 3
                $this->result->addSnippet(
175 3
                    new PlaceholderString("{$this->varName} = ::schema::object();\n",
176 3
                        array('::schema' => new TypeOf(Palette::schemaClass())))
177
                );
178
            } else {
179 6
                $this->result->addSnippet(
180 6
                    "{$this->varName}->type = 'object';\n"
181
                );
182
            }
183
184
        }
185
186
187 10
        if ($this->schema->additionalProperties !== null) {
188 4
            if ($this->schema->additionalProperties instanceof Schema) {
189 3
                $this->result->addSnippet(
190 3
                    $this->copyTo(new SchemaBuilder(
191 3
                        $this->schema->additionalProperties,
192 3
                        "{$this->varName}->additionalProperties",
193 3
                        $this->path . '->additionalProperties',
194 3
                        $this->phpBuilder
195 3
                    ))->build()
196
                );
197
            } else {
198 3
                $val = $this->schema->additionalProperties ? 'true' : 'false';
199 3
                $this->result->addSnippet(
200 3
                    "{$this->varName}->additionalProperties = $val;\n"
201
                );
202
            }
203
        }
204
205 10
        if ($this->schema->patternProperties !== null) {
206 4
            foreach ($this->schema->patternProperties as $pattern => $property) {
207 4
                $patternExp = var_export($pattern, true);
208 4
                $this->result->addSnippet(
209 4
                    $this->copyTo(new SchemaBuilder(
210 4
                        $property,
211 4
                        "\$patternProperty",
212 4
                        $this->path . '->patternProperties->{{$pattern}}',
213 4
                        $this->phpBuilder
214 4
                    ))->build()
215
                );
216 4
                $this->result->addSnippet("{$this->varName}->setPatternProperty({$patternExp}, \$patternProperty);\n");
217
218
            }
219
        }
220 10
    }
221
222
    /**
223
     * @throws Exception
224
     */
225 10
    private function processArray()
226
    {
227 10
        $schema = $this->schema;
228
229 10
        if (is_bool($schema->additionalItems)) {
230 2
            $val = $schema->additionalItems ? 'true' : 'false';
231 2
            $this->result->addSnippet(
232 2
                "{$this->varName}->additionalItems = $val;\n"
233
            );
234
        }
235
236 10
        $pathItems = 'items';
237 10
        if ($schema->items instanceof ClassStructure) { // todo better check for schema, `getJsonSchema` interface ?
238 2
            $items = array();
239 2
            $additionalItems = $schema->items;
240 2
            $pathItems = 'items';
241 10
        } elseif ($schema->items === null) { // items defaults to empty schema so everything is valid
242 10
            $items = array();
243 10
            $additionalItems = true;
244
        } else { // listed items
245
            $items = $schema->items;
246
            $additionalItems = $schema->additionalItems;
247
            $pathItems = 'additionalItems';
248
        }
249
250 10
        if ($items !== null || $additionalItems !== null) {
0 ignored issues
show
introduced by
The condition $items !== null is always true.
Loading history...
251 10
            if ($additionalItems instanceof ClassStructure) {
252 2
                $this->result->addSnippet(
253 2
                    $this->copyTo(new SchemaBuilder(
254 2
                        $additionalItems,
255 2
                        "{$this->varName}->{$pathItems}",
256 2
                        $this->path . '->' . $pathItems,
257 2
                        $this->phpBuilder
258 2
                    ))->build()
259
                );
260
            }
261
        }
262 10
    }
263
264 10
    private function processEnum()
265
    {
266 10
        if (!empty($this->schema->enum)) {
267 6
            $this->result->addSnippet(
268 6
                "{$this->varName}->enum = array(\n"
269
            );
270 6
            foreach ($this->schema->enum as $i => $enumItem) {
271 6
                if (isset($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i])) {
272
                    $name = PhpCode::makePhpConstantName($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i]);
273
                } else {
274 6
                    $name = PhpCode::makePhpConstantName($enumItem);
275
                }
276 6
                $value = var_export($enumItem, true);
277 6
                if ($this->saveEnumConstInClass !== null && is_scalar($enumItem) && !is_bool($enumItem)) {
278 2
                    $this->saveEnumConstInClass->addConstant(new PhpConstant($name, $enumItem));
279 2
                    $this->result->addSnippet(
280 2
                        "    self::$name,\n"
281
                    );
282
                } else {
283 6
                    $this->result->addSnippet(
284 6
                        "    $value,\n"
285
                    );
286
                }
287
288
            }
289 6
            $this->result->addSnippet(
290 6
                ");\n"
291
            );
292
293
        }
294 10
    }
295
296
    private $skip = [];
297
298
    public function skipProperty($name)
299
    {
300
        $this->skip[$name] = 1;
301
        return $this;
302
    }
303
304 8
    private function copyTo(SchemaBuilder $schemaBuilder)
305
    {
306 8
        $schemaBuilder->skip = $this->skip;
307 8
        $schemaBuilder->saveEnumConstInClass = $this->saveEnumConstInClass;
308 8
        return $schemaBuilder;
309
    }
310
311 10
    private function processOther()
312
    {
313 10
        static $skip = null;
314 10
        if ($skip === null) {
315 1
            $names = Schema::names();
316
            $skip = array(
317 1
                (string)$names->type => 1,
318 1
                '$ref' => 1,
319 1
                (string)$names->items => 1,
320 1
                (string)$names->additionalItems => 1,
321 1
                (string)$names->properties => 1,
322 1
                (string)$names->additionalProperties => 1,
323 1
                (string)$names->patternProperties => 1,
324 1
                (string)$names->allOf => 1, // @todo process
325 1
                (string)$names->anyOf => 1,
326 1
                (string)$names->oneOf => 1,
327 1
                (string)$names->not => 1,
328 1
                (string)$names->definitions => 1,
329 1
                (string)$names->enum => 1,
330 1
                (string)$names->fromRef => 1,
331 1
                (string)$names->originPath => 1,
332
            );
333
        }
334 10
        $schemaData = Schema::export($this->schema);
335 10
        foreach ((array)$schemaData as $key => $value) {
336 10
            if (isset($skip[$key])) {
337 10
                continue;
338
            }
339 6
            if (isset($this->skip[$key])) {
340
                continue;
341
            }
342
343
            //$this->result->addSnippet('/* ' . print_r($value, 1) . '*/' . "\n");
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% 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...
344
            //echo "{$this->varName}->{$key}\n";
345 6
            if ($value instanceof ObjectItem) {
346
                //$value = $value->jsonSerialize();
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
347
                $export = 'new \stdClass()';
348 6
            } elseif ($value instanceof \stdClass) {
349 2
                $export = '(object)' . PhpCode::varExport((array)$value);
350 6
            } elseif (is_string($value)) {
351 2
                $export = '"' . str_replace(array('\\', "\n", "\r", "\t", '"'), array('\\\\', '\n', '\r', '\t', '\"'), $value) . '"';
352
            } else {
353 6
                $export = PhpCode::varExport($value);
354
            }
355
356 6
            $key = PhpCode::makePhpName($key);
357 6
            $this->result->addSnippet(
358 6
                "{$this->varName}->{$key} = " . $export . ";\n"
359
            );
360
        }
361 10
    }
362
363 10
    private function processLogic()
364
    {
365 10
        if ($this->schema->not !== null) {
366 3
            $this->result->addSnippet(
367 3
                $this->copyTo(new SchemaBuilder(
368 3
                    $this->schema->not,
369 3
                    "{$this->varName}->not",
370 3
                    $this->path . '->not',
371 3
                    $this->phpBuilder
372 3
                ))->build()
373
            );
374
        }
375
376 10
        foreach (array('anyOf', 'oneOf', 'allOf') as $logic) {
377 10
            if ($this->schema->$logic !== null) {
378 6
                foreach ($this->schema->$logic as $index => $schema) {
379 6
                    $this->result->addSnippet(
380 6
                        $this->copyTo(new SchemaBuilder(
381 6
                            $schema,
382 6
                            "{$this->varName}->{$logic}[{$index}]",
383 6
                            $this->path . "->{$logic}[{$index}]",
384 6
                            $this->phpBuilder
385 10
                        ))->build()
386
                    );
387
                }
388
            }
389
        }
390 10
    }
391
392
    /**
393
     * @return PhpCode
394
     * @throws Exception
395
     */
396 10
    public function build()
397
    {
398 10
        $this->result = new PhpCode();
399
400 10
        if ($this->processNamedClass()) {
401 7
            return $this->result;
402
        }
403
404 10
        if ($this->processRef()) {
405 1
            return $this->result;
406
        }
407
408 10
        $this->processType();
409 10
        $this->processObject();
410 10
        $this->processArray();
411 10
        $this->processLogic();
412 10
        $this->processEnum();
413 10
        $this->processOther();
414 10
        $this->processFromRef();
415
416 10
        return $this->result;
417
418
    }
419
420 10
    private function processFromRef()
421
    {
422 10
        if ($this->phpBuilder->minimizeRefs) {
423 9
            if ($fromRefs = $this->schema->getFromRefs()) {
424 3
                $fromRef = $fromRefs[count($fromRefs) - 1];
0 ignored issues
show
Bug introduced by
$fromRefs of type string is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

424
                $fromRef = $fromRefs[count(/** @scrutinizer ignore-type */ $fromRefs) - 1];
Loading history...
425 3
                $value = var_export($fromRef, 1);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $value is correct as var_export($fromRef, 1) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
426 3
                $this->result->addSnippet("{$this->varName}->setFromRef($value);\n");
427
            }
428 9
            return;
429
        }
430 1
        if ($fromRefs = $this->schema->getFromRefs()) {
431 1
            foreach ($fromRefs as $fromRef) {
0 ignored issues
show
Bug introduced by
The expression $fromRefs of type string is not traversable.
Loading history...
432 1
                $value = var_export($fromRef, 1);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $value is correct as var_export($fromRef, 1) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
433 1
                $this->result->addSnippet("{$this->varName}->setFromRef($value);\n");
434
            }
435
        }
436 1
    }
437
438
    /**
439
     * @param boolean $skipProperties
440
     * @return $this
441
     */
442 10
    public function setSkipProperties($skipProperties)
443
    {
444 10
        $this->skipProperties = $skipProperties;
445 10
        return $this;
446
    }
447
448
    /**
449
     * @param PhpClass $saveEnumConstInClass
450
     * @return $this
451
     */
452 3
    public function setSaveEnumConstInClass($saveEnumConstInClass)
453
    {
454 3
        $this->saveEnumConstInClass = $saveEnumConstInClass;
455 3
        return $this;
456
    }
457
458
459
}