Completed
Push — master ( b44d8e...ea7f64 )
by Viacheslav
19s queued 15s
created

SchemaBuilder::processOther()   C

Complexity

Conditions 15
Paths 22

Size

Total Lines 71
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 47
CRAP Score 15.0152

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 52
nc 22
nop 0
dl 0
loc 71
ccs 47
cts 49
cp 0.9592
crap 15.0152
rs 5.9166
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

119
                new PlaceholderString($result . "\n", array('::schema' => /** @scrutinizer ignore-deprecated */ new ReferenceTypeOf(Palette::schemaClass())))
Loading history...
120
            );
121 11
        }
122
    }
123 11
124
    private function processNamedClass()
125 8
    {
126 8
        if (!$this->skipProperties
127
            //&& $this->schema->type === Type::OBJECT
128
            && $this->schema->properties !== null
129
        ) {
130
            $class = $this->phpBuilder->getClass($this->schema, $this->path);
131
            if ($this->schema->id === 'http://json-schema.org/draft-04/schema#') {
132 8
                $this->result->addSnippet(
133 8
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
134 8
                        array('::class' => new TypeOf(Palette::schemaClass())))
135
                );
136
            } else {
137 8
                $this->result->addSnippet(
138
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
139 11
                        array('::class' => new TypeOf($class)))
140
                );
141
            }
142 11
            return true;
143
        }
144 11
        return false;
145
    }
146 11
147 11
    private function processRef()
148
    {
149 1
        if (!$this->skipProperties
150 1
            //&& $this->schema->type === Type::OBJECT
151
            && !$this->phpBuilder->minimizeRefs
152
            && $this->schema->getFromRefs()
153
        ) {
154
            $class = $this->phpBuilder->getClass($this->schema, $this->path);
155
            if ($this->schema->id === 'http://json-schema.org/draft-04/schema#') {
156 1
                $this->result->addSnippet(
157 1
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
158 1
                        array('::class' => new TypeOf(Palette::schemaClass())))
159
                );
160
            } else {
161 1
                $this->result->addSnippet(
162
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
163 11
                        array('::class' => new TypeOf($class)))
164
                );
165
            }
166
            return true;
167
        }
168
        return false;
169
    }
170 11
171
172 11
    /**
173 7
     * @throws Exception
174 3
     */
175 3
    private function processObject()
176 3
    {
177
        if ($this->schema->type === Type::OBJECT) {
178
            if (!$this->skipProperties) {
179 7
                $this->result->addSnippet(
180 7
                    new PlaceholderString("{$this->varName} = ::schema::object();\n",
181
                        array('::schema' => new TypeOf(Palette::schemaClass())))
182
                );
183
            } else {
184
                $this->result->addSnippet(
185
                    "{$this->varName}->type = 'object';\n"
186
                );
187 11
            }
188 4
189 3
        }
190 3
191 3
192 3
        if ($this->schema->additionalProperties !== null) {
193 3
            if ($this->schema->additionalProperties instanceof Schema) {
194 3
                $this->result->addSnippet(
195 3
                    $this->copyTo(new SchemaBuilder(
196
                        $this->schema->additionalProperties,
197
                        "{$this->varName}->additionalProperties",
198 3
                        $this->path . '->additionalProperties',
199 3
                        $this->phpBuilder
200 3
                    ))->build()
201
                );
202
            } else {
203
                $val = $this->schema->additionalProperties ? 'true' : 'false';
204
                $this->result->addSnippet(
205 11
                    "{$this->varName}->additionalProperties = $val;\n"
206 4
                );
207 4
            }
208 4
        }
209 4
210 4
        if ($this->schema->patternProperties !== null) {
211 4
            foreach ($this->schema->patternProperties as $pattern => $property) {
212 4
                if ($property instanceof Schema) {
213 4
                    $patternExp = var_export($pattern, true);
214 4
                    $this->result->addSnippet(
215
                        $this->copyTo(new SchemaBuilder(
216 4
                            $property,
217
                            "\$patternProperty",
218
                            $this->path . "->patternProperties->{{$pattern}}",
219
                            $this->phpBuilder
220 11
                        ))->build()
221
                    );
222
                    $this->result->addSnippet("{$this->varName}->setPatternProperty({$patternExp}, \$patternProperty);\n");
223
                }
224
            }
225 11
        }
226
    }
227 11
228
    /**
229 11
     * @throws Exception
230 2
     */
231 2
    private function processArray()
232 2
    {
233
        $schema = $this->schema;
234
235
        if (is_bool($schema->additionalItems)) {
236 11
            $val = $schema->additionalItems ? 'true' : 'false';
237 11
            $this->result->addSnippet(
238 2
                "{$this->varName}->additionalItems = $val;\n"
239 2
            );
240 2
        }
241 11
242 11
        $pathItems = 'items';
243 11
        if ($schema->items instanceof ClassStructure) { // todo better check for schema, `getJsonSchema` interface ?
244
            $additionalItems = $schema->items;
0 ignored issues
show
Documentation Bug introduced by
It seems like $schema->items can also be of type Swaggest\JsonSchema\Structure\ClassStructure. However, the property $items is declared as type Swaggest\JsonSchema\Sche...a\SchemaContract[]|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
245
            $pathItems = 'items';
246
        } elseif ($schema->items === null) { // items defaults to empty schema so everything is valid
247
            $additionalItems = true;
248
        } else { // listed items
249
            $additionalItems = $schema->additionalItems;
250 11
            $pathItems = 'additionalItems';
251 11
        }
252 2
253 2
        if ($additionalItems instanceof ClassStructure) {
254 2
            $this->result->addSnippet(
255 2
                $this->copyTo(new SchemaBuilder(
256 2
                    $additionalItems,
257 2
                    "{$this->varName}->{$pathItems}",
258 2
                    $this->path . '->' . $pathItems,
259
                    $this->phpBuilder
260
                ))->build()
261
            );
262 11
        }
263
    }
264 11
265
    private function processEnum()
266 11
    {
267 7
        if (!empty($this->schema->enum)) {
268 7
            $this->result->addSnippet(
269
                "{$this->varName}->enum = array(\n"
270 7
            );
271 7
            foreach ($this->schema->enum as $i => $enumItem) {
272
                if (isset($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i])) {
273
                    $name = PhpCode::makePhpConstantName($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i]);
274 7
                } else {
275
                    $name = PhpCode::makePhpConstantName($enumItem);
276 7
                }
277 7
                $value = var_export($enumItem, true);
278 3
                if ($this->saveEnumConstInClass !== null && is_scalar($enumItem) && !is_bool($enumItem)) {
279 3
                    $this->saveEnumConstInClass->addConstant(new PhpConstant($name, $enumItem));
280 3
                    $this->result->addSnippet(
281
                        "    self::$name,\n"
282
                    );
283 6
                } else {
284 6
                    $this->result->addSnippet(
285
                        "    $value,\n"
286
                    );
287
                }
288
289 7
            }
290 7
            $this->result->addSnippet(
291
                ");\n"
292
            );
293
294 11
        }
295
    }
296
297
    private $skip = [];
298
299
    public function skipProperty($name)
300
    {
301
        $this->skip[$name] = 1;
302
        return $this;
303
    }
304 9
305
    private function copyTo(SchemaBuilder $schemaBuilder)
306 9
    {
307 9
        $schemaBuilder->skip = $this->skip;
308 9
        $schemaBuilder->saveEnumConstInClass = $this->saveEnumConstInClass;
309
        return $schemaBuilder;
310
    }
311 11
312
    private function processOther()
313 11
    {
314 11
        static $skip = null, $emptySchema = null, $names = null;
315 1
        if ($skip === null) {
316
            $emptySchema = new Schema();
317 1
            $names = Schema::names();
318 1
            $skip = array(
319 1
                $names->type => 1,
320 1
                Schema::PROP_REF => 1,
321 1
                $names->items => 1,
322 1
                $names->additionalItems => 1,
323 1
                $names->properties => 1,
324 1
                $names->additionalProperties => 1,
325 1
                $names->patternProperties => 1,
326 1
                $names->allOf => 1, // @todo process
327 1
                $names->anyOf => 1,
328 1
                $names->oneOf => 1,
329 1
                $names->not => 1,
330 1
                $names->definitions => 1,
331 1
                $names->enum => 1,
332 1
                $names->if => 1,
333 1
                $names->then => 1,
334 1
                $names->else => 1,
335
            );
336
        }
337 11
        $schemaData = Schema::export($this->schema);
338 11
        foreach ((array)$schemaData as $key => $value) {
339 11
            if (isset($skip[$key])) {
340 11
                continue;
341
            }
342 7
            if (isset($this->skip[$key])) {
343
                continue;
344
            }
345
346
            if (!property_exists($emptySchema, $key) && $key !== $names->const && $key[0] !== '$') {
347
                continue;
348 7
            }
349
350
            if ($names->required == $key && is_array($value)) {
351 7
                $export = "array(\n";
352 2
                foreach ($value as $item) {
353 7
                    if (PhpCode::makePhpName($item) === $item) {
354 2
                        $expItem = 'self::names()->' . $item;
355
                    } else {
356 7
                        $expItem = PhpCode::varExport($item);
357
                    }
358
                    $export .= '    ' . $expItem . ",\n";
359 7
                }
360 7
                $export .= ")";
361 7
                $this->result->addSnippet(
362
                    "{$this->varName}->{$key} = " . $export . ";\n"
363
                );
364 11
                continue;
365
            }
366 11
367
            //$this->result->addSnippet('/* ' . print_r($value, 1) . '*/' . "\n");
368 11
            //echo "{$this->varName}->{$key}\n";
369 11
            if ($value instanceof ObjectItem) {
370 11
                //$value = $value->jsonSerialize();
371 4
                $export = 'new \stdClass()';
372 4
            } elseif ($value instanceof \stdClass) {
373 4
                $export = '(object)' . PhpCode::varExport((array)$value);
374 4
            } elseif (is_string($value)) {
375 4
                $export = '"' . str_replace(array('\\', "\n", "\r", "\t", '"'), array('\\\\', '\n', '\r', '\t', '\"'), $value) . '"';
376 4
            } else {
377 4
                $export = PhpCode::varExport($value);
378
            }
379
380
            $key = PhpCode::makePhpName($key);
381
            $this->result->addSnippet(
382
                "{$this->varName}->{$key} = " . $export . ";\n"
383 11
            );
384 11
        }
385 6
    }
386 6
387 6
    private function processLogic()
388 6
    {
389 6
        $names = Schema::names();
390 6
        /** @var string $keyword */
391 6
        foreach (array($names->not, $names->if, $names->then, $names->else) as $keyword) {
392 6
            if ($this->schema->$keyword !== null) {
393
                $this->result->addSnippet(
394 6
                    $this->copyTo(new SchemaBuilder(
395 6
                        $this->schema->$keyword,
396 6
                        "{$this->varName}->{$keyword}",
397 6
                        $this->path . '->' . $keyword,
0 ignored issues
show
Bug introduced by
Are you sure $keyword of type Swaggest\JsonSchema\SchemaContract|string can be used in concatenation? ( Ignorable by Annotation )

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

397
                        $this->path . '->' . /** @scrutinizer ignore-type */ $keyword,
Loading history...
398 6
                        $this->phpBuilder
399 6
                    ))->build()
400 6
                );
401
            }
402 2
403 2
        }
404 2
405
        foreach (array($names->anyOf, $names->oneOf, $names->allOf) as $keyword) {
406
            if ($this->schema->$keyword !== null) {
407
                foreach ($this->schema->$keyword as $index => $schema) {
408
                    $varName = '$' . PhpCode::makePhpName("{$this->varName}->{$keyword}[{$index}]");
409
                    $schemaInit = $this->copyTo(new SchemaBuilder(
410
                        $schema,
411
                        $varName,
412 11
                        $this->path . "->{$keyword}[{$index}]",
413
                        $this->phpBuilder
414
                    ))->build();
415
416
                    if (count($schemaInit->snippets) === 1) { // Init in single statement can be just assigned.
417
                        $this->result->addSnippet($this->copyTo(new SchemaBuilder(
418 11
                            $schema,
419
                            "{$this->varName}->{$keyword}[{$index}]",
420 11
                            $this->path . "->{$keyword}[{$index}]",
421
                            $this->phpBuilder
422 11
                        ))->build());
423 8
                    } else {
424
                        $this->result->addSnippet($schemaInit);
425
                        $this->result->addSnippet(<<<PHP
426 11
{$this->varName}->{$keyword}[{$index}] = {$varName};
427 1
428
PHP
429
                        );
430 11
                    }
431 11
                }
432 11
            }
433 11
        }
434 11
    }
435 11
436 11
    /**
437
     * @return PhpCode
438 11
     * @throws Exception
439
     */
440
    public function build()
441
    {
442 11
        $this->result = new PhpCode();
443
444 11
        if ($this->processNamedClass()) {
445 10
            return $this->result;
446 3
        }
447 3
448 3
        if ($this->processRef()) {
449
            return $this->result;
450 10
        }
451
452 1
        $this->processType();
453 1
        $this->processObject();
454 1
        $this->processArray();
455 1
        $this->processLogic();
456
        $this->processEnum();
457
        $this->processOther();
458 1
        $this->processFromRef();
459
460
        return $this->result;
461
462
    }
463
464 11
    private function processFromRef()
465
    {
466 11
        if ($this->phpBuilder->minimizeRefs) {
467 11
            if ($fromRefs = $this->schema->getFromRefs()) {
468
                $fromRef = $fromRefs[count($fromRefs) - 1];
469
                $value = var_export($fromRef, true);
470
                $this->result->addSnippet("{$this->varName}->setFromRef($value);\n");
471
            }
472
            return;
473
        }
474 4
        if ($fromRefs = $this->schema->getFromRefs()) {
475
            foreach ($fromRefs as $fromRef) {
476 4
                $value = var_export($fromRef, true);
477 4
                $this->result->addSnippet("{$this->varName}->setFromRef($value);\n");
478
            }
479
        }
480
    }
481
482
    /**
483
     * @param boolean $skipProperties
484
     * @return $this
485
     */
486
    public function setSkipProperties($skipProperties)
487
    {
488
        $this->skipProperties = $skipProperties;
489
        return $this;
490
    }
491
492
    /**
493
     * @param PhpClass $saveEnumConstInClass
494
     * @return $this
495
     */
496
    public function setSaveEnumConstInClass($saveEnumConstInClass)
497
    {
498
        $this->saveEnumConstInClass = $saveEnumConstInClass;
499
        return $this;
500
    }
501
502
503
}