Passed
Pull Request — master (#6)
by Viacheslav
02:23
created

SchemaBuilder::setSkipProperties()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 11
    public function __construct($schema, $varName, $path, PhpBuilder $phpBuilder, $createVarName = true)
49
    {
50 11
        $this->schema = $schema;
51 11
        $this->varName = $varName;
52 11
        $this->phpBuilder = $phpBuilder;
53 11
        $this->path = $path;
54 11
        $this->createVarName = $createVarName;
55 11
    }
56
57 11
    private function processType()
58
    {
59 11
        if ($this->schema->type !== null) {
60 7
            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 6
                    $result = $this->createVarName
81 6
                        ? "{$this->varName} = ::schema::string();"
82 6
                        : "{$this->varName}->type = ::schema::STRING;";
83 6
                    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 7
                    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 7
                        : "{$this->varName}->type = $types;";
105
            }
106
        } else {
107 8
            if ($this->createVarName) {
108 8
                $result = "{$this->varName} = new ::schema();";
109
            }
110
        }
111
112 11
        if (isset($result)) {
113 11
            $this->result->addSnippet(
114 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

114
                new PlaceholderString($result . "\n", array('::schema' => /** @scrutinizer ignore-deprecated */ new ReferenceTypeOf(Palette::schemaClass())))
Loading history...
115
            );
116
        }
117 11
    }
118
119 11
    private function processNamedClass()
120
    {
121 11
        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 11
            && $this->schema->properties !== null
124
        ) {
125 8
            $class = $this->phpBuilder->getClass($this->schema, $this->path);
126 8
            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 8
                $this->result->addSnippet(
133 8
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
134 8
                        array('::class' => new TypeOf($class)))
135
                );
136
            }
137 8
            return true;
138
        }
139 11
        return false;
140
    }
141
142 11
    private function processRef()
143
    {
144 11
        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 11
            && !$this->phpBuilder->minimizeRefs
147 11
            && $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 11
        return false;
164
    }
165
166
167
    /**
168
     * @throws Exception
169
     */
170 11
    private function processObject()
171
    {
172 11
        if ($this->schema->type === Type::OBJECT) {
173 7
            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 7
                $this->result->addSnippet(
180 7
                    "{$this->varName}->type = 'object';\n"
181
                );
182
            }
183
184
        }
185
186
187 11
        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 11
        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 11
    }
221
222
    /**
223
     * @throws Exception
224
     */
225 11
    private function processArray()
226
    {
227 11
        $schema = $this->schema;
228
229 11
        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 11
        $pathItems = 'items';
237 11
        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 11
        } elseif ($schema->items === null) { // items defaults to empty schema so everything is valid
242 11
            $items = array();
243 11
            $additionalItems = true;
244
        } else { // listed items
245
            $items = $schema->items;
246
            $additionalItems = $schema->additionalItems;
247
            $pathItems = 'additionalItems';
248
        }
249
250 11
        if ($items !== null || $additionalItems !== null) {
0 ignored issues
show
introduced by
The condition $items !== null is always true.
Loading history...
251 11
            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 11
    }
263
264 11
    private function processEnum()
265
    {
266 11
        if (!empty($this->schema->enum)) {
267 7
            $this->result->addSnippet(
268 7
                "{$this->varName}->enum = array(\n"
269
            );
270 7
            foreach ($this->schema->enum as $i => $enumItem) {
271 7
                if (isset($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i])) {
272
                    $name = PhpCode::makePhpConstantName($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i]);
273
                } else {
274 7
                    $name = PhpCode::makePhpConstantName($enumItem);
275
                }
276 7
                $value = var_export($enumItem, true);
277 7
                if ($this->saveEnumConstInClass !== null && is_scalar($enumItem) && !is_bool($enumItem)) {
278 3
                    $this->saveEnumConstInClass->addConstant(new PhpConstant($name, $enumItem));
279 3
                    $this->result->addSnippet(
280 3
                        "    self::$name,\n"
281
                    );
282
                } else {
283 6
                    $this->result->addSnippet(
284 7
                        "    $value,\n"
285
                    );
286
                }
287
288
            }
289 7
            $this->result->addSnippet(
290 7
                ");\n"
291
            );
292
293
        }
294 11
    }
295
296
    private $skip = [];
297
298
    public function skipProperty($name)
299
    {
300
        $this->skip[$name] = 1;
301
        return $this;
302
    }
303
304 9
    private function copyTo(SchemaBuilder $schemaBuilder)
305
    {
306 9
        $schemaBuilder->skip = $this->skip;
307 9
        $schemaBuilder->saveEnumConstInClass = $this->saveEnumConstInClass;
308 9
        return $schemaBuilder;
309
    }
310
311 11
    private function processOther()
312
    {
313 11
        static $skip = null;
314 11
        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->if => 1,
331 1
                (string)$names->then => 1,
332 1
                (string)$names->else => 1,
333 1
                (string)$names->fromRef => 1,
334 1
                (string)$names->originPath => 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
            //$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...
347
            //echo "{$this->varName}->{$key}\n";
348 7
            if ($value instanceof ObjectItem) {
349
                //$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...
350
                $export = 'new \stdClass()';
351 7
            } elseif ($value instanceof \stdClass) {
352 2
                $export = '(object)' . PhpCode::varExport((array)$value);
353 7
            } elseif (is_string($value)) {
354 2
                $export = '"' . str_replace(array('\\', "\n", "\r", "\t", '"'), array('\\\\', '\n', '\r', '\t', '\"'), $value) . '"';
355
            } else {
356 7
                $export = PhpCode::varExport($value);
357
            }
358
359 7
            $key = PhpCode::makePhpName($key);
360 7
            $this->result->addSnippet(
361 7
                "{$this->varName}->{$key} = " . $export . ";\n"
362
            );
363
        }
364 11
    }
365
366 11
    private function processLogic()
367
    {
368 11
        $names = Schema::names();
369 11
        foreach (array($names->not, $names->if, $names->then, $names->else) as $keyword) {
370 11
            if ($this->schema->$keyword !== null) {
371 4
                $this->result->addSnippet(
372 4
                    $this->copyTo(new SchemaBuilder(
373 4
                        $this->schema->$keyword,
374 4
                        "{$this->varName}->{$keyword}",
375 4
                        $this->path . '->' . $keyword,
0 ignored issues
show
Bug introduced by
Are you sure $keyword of type Swaggest\JsonSchema\Schema|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

375
                        $this->path . '->' . /** @scrutinizer ignore-type */ $keyword,
Loading history...
376 4
                        $this->phpBuilder
377 11
                    ))->build()
378
                );
379
            }
380
381
        }
382
383 11
        foreach (array($names->anyOf, $names->oneOf, $names->allOf) as $keyword) {
384 11
            if ($this->schema->$keyword !== null) {
385 6
                foreach ($this->schema->$keyword as $index => $schema) {
386 6
                    $this->result->addSnippet(
387 6
                        $this->copyTo(new SchemaBuilder(
388 6
                            $schema,
389 6
                            "{$this->varName}->{$keyword}[{$index}]",
390 6
                            $this->path . "->{$keyword}[{$index}]",
391 6
                            $this->phpBuilder
392 11
                        ))->build()
393
                    );
394
                }
395
            }
396
        }
397 11
    }
398
399
    /**
400
     * @return PhpCode
401
     * @throws Exception
402
     */
403 11
    public function build()
404
    {
405 11
        $this->result = new PhpCode();
406
407 11
        if ($this->processNamedClass()) {
408 8
            return $this->result;
409
        }
410
411 11
        if ($this->processRef()) {
412 1
            return $this->result;
413
        }
414
415 11
        $this->processType();
416 11
        $this->processObject();
417 11
        $this->processArray();
418 11
        $this->processLogic();
419 11
        $this->processEnum();
420 11
        $this->processOther();
421 11
        $this->processFromRef();
422
423 11
        return $this->result;
424
425
    }
426
427 11
    private function processFromRef()
428
    {
429 11
        if ($this->phpBuilder->minimizeRefs) {
430 10
            if ($fromRefs = $this->schema->getFromRefs()) {
431 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

431
                $fromRef = $fromRefs[count(/** @scrutinizer ignore-type */ $fromRefs) - 1];
Loading history...
432 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...
433 3
                $this->result->addSnippet("{$this->varName}->setFromRef($value);\n");
434
            }
435 10
            return;
436
        }
437 1
        if ($fromRefs = $this->schema->getFromRefs()) {
438 1
            foreach ($fromRefs as $fromRef) {
0 ignored issues
show
Bug introduced by
The expression $fromRefs of type string is not traversable.
Loading history...
439 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...
440 1
                $this->result->addSnippet("{$this->varName}->setFromRef($value);\n");
441
            }
442
        }
443 1
    }
444
445
    /**
446
     * @param boolean $skipProperties
447
     * @return $this
448
     */
449 11
    public function setSkipProperties($skipProperties)
450
    {
451 11
        $this->skipProperties = $skipProperties;
452 11
        return $this;
453
    }
454
455
    /**
456
     * @param PhpClass $saveEnumConstInClass
457
     * @return $this
458
     */
459 4
    public function setSaveEnumConstInClass($saveEnumConstInClass)
460
    {
461 4
        $this->saveEnumConstInClass = $saveEnumConstInClass;
462 4
        return $this;
463
    }
464
465
466
}