Completed
Push — master ( d75c1f...eed677 )
by Viacheslav
12s
created

SchemaBuilder::copyTo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
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\JsonSchema;
9
use Swaggest\JsonSchema\Schema;
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 $schema
44
     * @param string $varName
45
     * @param string $path
46
     * @param PhpBuilder $phpBuilder
47
     * @param bool $createVarName
48
     */
49 4
    public function __construct($schema, $varName, $path, PhpBuilder $phpBuilder, $createVarName = true)
50
    {
51 4
        $this->schema = $schema;
52 4
        $this->varName = $varName;
53 4
        $this->phpBuilder = $phpBuilder;
54 4
        $this->path = $path;
55 4
        $this->createVarName = $createVarName;
56 4
    }
57
58 4
    private function processType()
59
    {
60 4
        if ($this->schema->type !== null) {
61 4
            switch ($this->schema->type) {
62
                case Type::INTEGER:
63 3
                    $result = $this->createVarName
64 2
                        ? "{$this->varName} = ::schema::integer();"
65 3
                        : "{$this->varName}->type = ::schema::INTEGER;";
66 3
                    break;
67
68
                case Type::NUMBER:
69 4
                    $result = $this->createVarName
70 3
                        ? "{$this->varName} = ::schema::number();"
71 4
                        : "{$this->varName}->type = ::schema::NUMBER;";
72 4
                    break;
73
74
                case Type::BOOLEAN:
75 3
                    $result = $this->createVarName
76 3
                        ? "{$this->varName} = ::schema::boolean();"
77 3
                        : "{$this->varName}->type = ::schema::BOOLEAN;";
78 3
                    break;
79
80
                case Type::STRING:
81 3
                    $result = $this->createVarName
82 3
                        ? "{$this->varName} = ::schema::string();"
83 3
                        : "{$this->varName}->type = ::schema::STRING;";
84 3
                    break;
85
86
                case Type::ARR:
87 2
                    $result = $this->createVarName
88 2
                        ? "{$this->varName} = ::schema::arr();"
89 2
                        : "{$this->varName}->type = ::schema::_ARRAY;";
90 2
                    break;
91
92
                case Type::OBJECT:
93 4
                    return;
94
95
                case Type::NULL:
96
                    $result = $this->createVarName
97
                        ? "{$this->varName} = ::schema::null();"
98
                        : "{$this->varName}->type = ::schema::NULL;";
99
                    break;
100
101
                default:
102
                    $types = var_export($this->schema->type, true);
103
                    $result = $this->createVarName
104
                        ? "{$this->varName} = (new ::schema())->setType($types);"
105 4
                        : "{$this->varName}->type = $types;";
106
            }
107
        } else {
108 2
            if ($this->createVarName) {
109 2
                $result = "{$this->varName} = new ::schema();";
110
            }
111
        }
112
113 4
        if (isset($result)) {
114 4
            $this->result->addSnippet(
115 4
                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

115
                new PlaceholderString($result . "\n", array('::schema' => /** @scrutinizer ignore-deprecated */ new ReferenceTypeOf(Palette::schemaClass())))
Loading history...
116
            );
117
        }
118 4
    }
119
120 4
    private function processNamedClass()
121
    {
122 4
        if (!$this->skipProperties
123
            //&& $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...
124 4
            && $this->schema->properties !== null
125
        ) {
126 3
            $class = $this->phpBuilder->getClass($this->schema, $this->path);
127 3
            if ($this->schema->id === 'http://json-schema.org/draft-04/schema#') {
128
                $this->result->addSnippet(
129
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
130
                        array('::class' => new TypeOf(Palette::schemaClass())))
131
                );
132
            } else {
133 3
                $this->result->addSnippet(
134 3
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
135 3
                        array('::class' => new TypeOf($class)))
136
                );
137
            }
138 3
            return true;
139
        }
140 4
        return false;
141
    }
142
143 4
    private function processRef()
144
    {
145 4
        if (!$this->skipProperties
146
            //&& $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...
147 4
            && !$this->phpBuilder->minimizeRefs
148 4
            && $this->schema->getFromRefs()
149
        ) {
150 1
            $class = $this->phpBuilder->getClass($this->schema, $this->path);
151 1
            if ($this->schema->id === 'http://json-schema.org/draft-04/schema#') {
152
                $this->result->addSnippet(
153
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
154
                        array('::class' => new TypeOf(Palette::schemaClass())))
155
                );
156
            } else {
157 1
                $this->result->addSnippet(
158 1
                    new PlaceholderString("{$this->varName} = ::class::schema();\n",
159 1
                        array('::class' => new TypeOf($class)))
160
                );
161
            }
162 1
            return true;
163
        }
164 4
        return false;
165
    }
166
167
168
    /**
169
     * @throws Exception
170
     */
171 4
    private function processObject()
172
    {
173 4
        if ($this->schema->type === Type::OBJECT) {
174 4
            if (!$this->skipProperties) {
175 2
                $this->result->addSnippet(
176 2
                    new PlaceholderString("{$this->varName} = ::schema::object();\n",
177 2
                        array('::schema' => new TypeOf(Palette::schemaClass())))
178
                );
179
            } else {
180 4
                $this->result->addSnippet(
181 4
                    "{$this->varName}->type = 'object';\n"
182
                );
183
            }
184
185
        }
186
187
188 4
        if ($this->schema->additionalProperties !== null) {
189 2
            if ($this->schema->additionalProperties instanceof Schema) {
190 2
                $this->result->addSnippet(
191 2
                    $this->copyTo(new SchemaBuilder(
192 2
                        $this->schema->additionalProperties,
193 2
                        "{$this->varName}->additionalProperties",
194 2
                        $this->path . '->additionalProperties',
195 2
                        $this->phpBuilder
196 2
                    ))->build()
197
                );
198
            } else {
199 2
                $val = $this->schema->additionalProperties ? 'true' : 'false';
200 2
                $this->result->addSnippet(
201 2
                    "{$this->varName}->additionalProperties = $val;\n"
202
                );
203
            }
204
        }
205
206 4
        if ($this->schema->patternProperties !== null) {
207 2
            foreach ($this->schema->patternProperties as $pattern => $property) {
208 2
                $patternExp = var_export($pattern, true);
209 2
                $this->result->addSnippet(
210 2
                    $this->copyTo(new SchemaBuilder(
211 2
                        $property,
212 2
                        "\$patternProperty",
213 2
                        $this->path . '->patternProperties->{{$pattern}}',
214 2
                        $this->phpBuilder
215 2
                    ))->build()
216
                );
217 2
                $this->result->addSnippet("{$this->varName}->setPatternProperty({$patternExp}, \$patternProperty);\n");
218
219
            }
220
        }
221 4
    }
222
223
    /**
224
     * @throws Exception
225
     */
226 4
    private function processArray()
227
    {
228 4
        $schema = $this->schema;
229
230 4
        if (is_bool($schema->additionalItems)) {
231 2
            $val = $schema->additionalItems ? 'true' : 'false';
232 2
            $this->result->addSnippet(
233 2
                "{$this->varName}->additionalItems = $val;\n"
234
            );
235
        }
236
237 4
        $pathItems = 'items';
238 4
        if ($schema->items instanceof ClassStructure) { // todo better check for schema, `getJsonSchema` interface ?
239 2
            $items = array();
240 2
            $additionalItems = $schema->items;
241 2
            $pathItems = 'items';
242 4
        } elseif ($schema->items === null) { // items defaults to empty schema so everything is valid
243 4
            $items = array();
244 4
            $additionalItems = true;
245
        } else { // listed items
246
            $items = $schema->items;
247
            $additionalItems = $schema->additionalItems;
248
            $pathItems = 'additionalItems';
249
        }
250
251 4
        if ($items !== null || $additionalItems !== null) {
0 ignored issues
show
introduced by
The condition $items !== null is always true.
Loading history...
252 4
            if ($additionalItems instanceof ClassStructure) {
253 2
                $this->result->addSnippet(
254 2
                    $this->copyTo(new SchemaBuilder(
255 2
                        $additionalItems,
256 2
                        "{$this->varName}->{$pathItems}",
257 2
                        $this->path . '->' . $pathItems,
258 2
                        $this->phpBuilder
259 2
                    ))->build()
260
                );
261
            }
262
        }
263 4
    }
264
265 4
    private function processEnum()
266
    {
267 4
        if (!empty($this->schema->enum)) {
268 2
            $this->result->addSnippet(
269 2
                "{$this->varName}->enum = array(\n"
270
            );
271 2
            foreach ($this->schema->enum as $i => $enumItem) {
272 2
                if (isset($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i])) {
273
                    $name = PhpCode::makePhpConstantName($this->schema->{Schema::ENUM_NAMES_PROPERTY}[$i]);
274
                } else {
275 2
                    $name = PhpCode::makePhpConstantName($enumItem);
276
                }
277 2
                $value = var_export($enumItem, true);
278 2
                if ($this->saveEnumConstInClass !== null && is_scalar($enumItem) && !is_bool($enumItem)) {
279 2
                    $this->saveEnumConstInClass->addConstant(new PhpConstant($name, $enumItem));
280 2
                    $this->result->addSnippet(
281 2
                        "    self::$name,\n"
282
                    );
283
                } else {
284 2
                    $this->result->addSnippet(
285 2
                        "    $value,\n"
286
                    );
287
                }
288
289
            }
290 2
            $this->result->addSnippet(
291 2
                ");\n"
292
            );
293
294
        }
295 4
    }
296
297
    private $skip = [];
298
299
    public function skipProperty($name)
300
    {
301
        $this->skip[$name] = 1;
302
        return $this;
303
    }
304
305 2
    private function copyTo(SchemaBuilder $schemaBuilder)
306
    {
307 2
        $schemaBuilder->skip = $this->skip;
308 2
        $schemaBuilder->saveEnumConstInClass = $this->saveEnumConstInClass;
309 2
        return $schemaBuilder;
310
    }
311
312 4
    private function processOther()
313
    {
314 4
        static $skip = null;
315 4
        if ($skip === null) {
316 1
            $names = Schema::names();
317
            $skip = array(
318 1
                (string)$names->type => 1,
319 1
                '$ref' => 1,
320 1
                (string)$names->items => 1,
321 1
                (string)$names->additionalItems => 1,
322 1
                (string)$names->properties => 1,
323 1
                (string)$names->additionalProperties => 1,
324 1
                (string)$names->patternProperties => 1,
325 1
                (string)$names->allOf => 1, // @todo process
326 1
                (string)$names->anyOf => 1,
327 1
                (string)$names->oneOf => 1,
328 1
                (string)$names->not => 1,
329 1
                (string)$names->definitions => 1,
330 1
                (string)$names->enum => 1,
331 1
                (string)$names->fromRef => 1,
332 1
                (string)$names->originPath => 1,
333
            );
334
        }
335 4
        $schemaData = Schema::export($this->schema);
336 4
        foreach ((array)$schemaData as $key => $value) {
337 4
            if (isset($skip[$key])) {
338 4
                continue;
339
            }
340 2
            if (isset($this->skip[$key])) {
341
                continue;
342
            }
343
344
            //$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...
345
            //echo "{$this->varName}->{$key}\n";
346 2
            if ($value instanceof ObjectItem) {
347
                //$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...
348
                $export = 'new \stdClass()';
349 2
            } elseif ($value instanceof \stdClass) {
350 2
                $export = '(object)' . var_export((array)$value, 1);
0 ignored issues
show
Bug introduced by
Are you sure the usage of var_export((array)$value, 1) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
351 2
            } elseif (is_string($value)) {
352 2
                $export = '"' . str_replace(array('\\', "\n", "\r", "\t", '"'), array('\\\\', '\n', '\r', '\t', '\"'), $value) . '"';
353
            } else {
354 2
                $export = var_export($value, 1);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $export is correct as var_export($value, 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...
355
            }
356
357 2
            $key = PhpCode::makePhpName($key);
358 2
            $this->result->addSnippet(
359 2
                "{$this->varName}->{$key} = " . $export . ";\n"
360
            );
361
        }
362 4
    }
363
364 4
    private function processLogic()
365
    {
366 4
        if ($this->schema->not !== null) {
367 2
            $this->result->addSnippet(
368 2
                $this->copyTo(new SchemaBuilder(
369 2
                    $this->schema->not,
370 2
                    "{$this->varName}->not",
371 2
                    $this->path . '->not',
372 2
                    $this->phpBuilder
373 2
                ))->build()
374
            );
375
        }
376
377 4
        foreach (array('anyOf', 'oneOf', 'allOf') as $logic) {
378 4
            if ($this->schema->$logic !== null) {
379 2
                foreach ($this->schema->$logic as $index => $schema) {
380 2
                    $this->result->addSnippet(
381 2
                        $this->copyTo(new SchemaBuilder(
382 2
                            $schema,
383 2
                            "{$this->varName}->{$logic}[{$index}]",
384 2
                            $this->path . "->{$logic}[{$index}]",
385 2
                            $this->phpBuilder
386 4
                        ))->build()
387
                    );
388
                }
389
            }
390
        }
391 4
    }
392
393
    /**
394
     * @return PhpCode
395
     * @throws Exception
396
     */
397 4
    public function build()
398
    {
399 4
        $this->result = new PhpCode();
400
401 4
        if ($this->processNamedClass()) {
402 3
            return $this->result;
403
        }
404
405 4
        if ($this->processRef()) {
406 1
            return $this->result;
407
        }
408
409 4
        $this->processType();
410 4
        $this->processObject();
411 4
        $this->processArray();
412 4
        $this->processLogic();
413 4
        $this->processEnum();
414 4
        $this->processOther();
415 4
        $this->processFromRef();
416
417 4
        return $this->result;
418
419
    }
420
421 4
    private function processFromRef()
422
    {
423 4
        if ($this->phpBuilder->minimizeRefs) {
424 3
            if ($fromRefs = $this->schema->getFromRefs()) {
425 2
                $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

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