Completed
Push — master ( d6b8fe...c770f3 )
by Viacheslav
17:29 queued 07:42
created

TypeBuilder   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 155
c 2
b 0
f 0
dl 0
loc 283
rs 3.52
wmc 61

5 Methods

Rating   Name   Duplication   Size   Complexity  
A description() 0 8 2
F getTypeString() 0 189 48
A typeName() 0 13 4
A __construct() 0 3 1
B makeObjectTypeDef() 0 44 6

How to fix   Complexity   

Complex Class

Complex classes like TypeBuilder 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 TypeBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Swaggest\PhpCodeBuilder\JSDoc;
4
5
use Swaggest\CodeBuilder\CodeBuilder;
6
use Swaggest\JsonSchema\Schema;
7
use Swaggest\PhpCodeBuilder\PhpCode;
8
9
class TypeBuilder
10
{
11
    /** @var \SplObjectStorage */
12
    private $processed;
13
14
    public $trimNamePrefix = [
15
        '#/definitions'
16
    ];
17
18
    public $addNamePrefix = '';
19
20
    public $file = '';
21
22
    public function __construct()
23
    {
24
        $this->processed = new \SplObjectStorage();
25
    }
26
27
    /**
28
     * @param Schema|boolean $schema
29
     * @param string $path
30
     * @return string
31
     */
32
    public function getTypeString($schema, $path = '')
33
    {
34
        $schema = Schema::unboolSchema($schema);
35
36
        $isOptional = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $isOptional is dead and can be removed.
Loading history...
37
        $isObject = false;
38
        $isArray = false;
39
        $isBoolean = false;
40
        $isString = false;
41
        $isNumber = false;
42
43
        if ($schema->const !== null) {
44
            return '(' . var_export($schema->const, true) . ')';
45
        }
46
47
        if (!empty($schema->enum)) {
48
            $res = '';
49
            foreach ($schema->enum as $value) {
50
                $res .= var_export($value, true) . '|';
51
            }
52
            return '(' . substr($res, 0, -1) . ')';
53
        }
54
55
        if (!empty($schema->getFromRefs())) {
56
            $refs = $schema->getFromRefs();
57
            $path = $refs[0];
58
        }
59
60
        $type = $schema->type;
61
        if ($type === null) {
0 ignored issues
show
introduced by
The condition $type === null is always false.
Loading history...
62
            $type = [];
63
64
            if (!empty($schema->properties) || !empty($schema->additionalProperties) || !empty($schema->patternProperties)) {
65
                $type[] = Schema::OBJECT;
66
            }
67
68
            if (!empty($schema->items) || !empty($schema->additionalItems)) {
69
                $type[] = Schema::_ARRAY;
70
            }
71
        }
72
73
        if (!is_array($type)) {
74
            $type = [$type];
75
        }
76
77
        $or = [];
78
79
        if ($schema->oneOf !== null) {
80
            foreach ($schema->oneOf as $i => $item) {
81
                $or[] = $this->getTypeString($item, $path . '/oneOf/' . $i);
82
            }
83
        }
84
85
        if ($schema->anyOf !== null) {
86
            foreach ($schema->anyOf as $i => $item) {
87
                $or[] = $this->getTypeString($item, $path . '/anyOf/' . $i);
88
            }
89
        }
90
91
        if ($schema->allOf !== null) {
92
            foreach ($schema->allOf as $i => $item) {
93
                $or[] = $this->getTypeString($item, $path . '/allOf/' . $i);
94
            }
95
        }
96
97
        if ($schema->then !== null) {
98
            $or[] = $this->getTypeString($schema->then, $path . '/then');
99
        }
100
101
        if ($schema->else !== null) {
102
            $or[] = $this->getTypeString($schema->else, $path . '/else');
103
        }
104
105
        foreach ($type as $i => $t) {
106
            switch ($t) {
107
                case Schema::NULL:
108
                    $isOptional = true;
109
                    break;
110
111
                case Schema::OBJECT:
112
                    $isObject = true;
113
                    break;
114
115
                case Schema::_ARRAY:
116
                    $isArray = true;
117
                    break;
118
119
                case Schema::NUMBER:
120
                case Schema::INTEGER:
121
                    $isNumber = true;
122
                    break;
123
124
                case Schema::STRING:
125
                    $isString = true;
126
                    break;
127
128
                case Schema::BOOLEAN:
129
                    $isBoolean = true;
130
                    break;
131
132
            }
133
        }
134
135
        if ($isObject) {
136
            $typeAdded = false;
137
138
            if (!empty($schema->properties)) {
139
                if ($this->processed->contains($schema)) {
140
                    $or [] = $this->processed->offsetGet($schema);
141
                    $typeAdded = true;
142
                } else {
143
                    if ($schema instanceof Schema) {
144
                        $typeName = $this->typeName($schema, $path);
145
                        $this->makeObjectTypeDef($schema, $path);
146
147
                        $or [] = $typeName;
148
                        $typeAdded = true;
149
                    }
150
                }
151
152
            }
153
154
            if ($schema->additionalProperties instanceof Schema) {
155
                $typeName = $this->getTypeString($schema->additionalProperties, $path . '/additionalProperties');
156
                $or [] = "object<string, $typeName>";
157
                $typeAdded = true;
158
            }
159
160
            if (!empty($schema->patternProperties)) {
161
                foreach ($schema->patternProperties as $pattern => $propertySchema) {
162
                    if ($propertySchema instanceof Schema) {
163
                        $typeName = $this->getTypeString($propertySchema, $path . '/patternProperties/' . $pattern);
164
                        $or [] = $typeName;
165
                        $typeAdded = true;
166
                    }
167
                }
168
            }
169
170
            if (!$typeAdded) {
171
                $or [] = 'object';
172
            }
173
        }
174
175
        if ($isArray) {
176
            $typeAdded = false;
177
178
            if ($schema->items instanceof Schema) {
179
                $typeName = $this->getTypeString($schema->items, $path . '/items');
180
                $or [] = "array<$typeName>";
181
                $typeAdded = true;
182
            }
183
184
            if ($schema->additionalItems instanceof Schema) {
185
                $typeName = $this->getTypeString($schema->additionalItems, $path . '/additionalItems');
186
                $or [] = "array<$typeName>";
187
                $typeAdded = true;
188
            }
189
190
            if (!$typeAdded) {
191
                $or [] = 'array';
192
            }
193
        }
194
195
        if ($isString) {
196
            $or [] = 'string';
197
        }
198
199
        if ($isNumber) {
200
            $or [] = 'number';
201
        }
202
203
        if ($isBoolean) {
204
            $or [] = 'boolean';
205
        }
206
207
        $res = '';
208
        foreach ($or as $item) {
209
            if (!empty($item) && $item !== '*') {
210
                $res .= '|' . $item;
211
            }
212
        }
213
214
        if ($res !== '') {
215
            $res = substr($res, 1);
216
        } else {
217
            $res = '*';
218
        }
219
220
        return $res;
221
    }
222
223
    private function typeName(Schema $schema, $path)
224
    {
225
        if ($fromRefs = $schema->getFromRefs()) {
226
            $path = $fromRefs[count($fromRefs) - 1];
227
        }
228
229
        foreach ($this->trimNamePrefix as $prefix) {
230
            if ($prefix === substr($path, 0, strlen($prefix))) {
231
                $path = substr($path, strlen($prefix));
232
            }
233
        }
234
235
        return PhpCode::makePhpName($this->addNamePrefix . '_' . $path, false);
236
    }
237
238
    private function makeObjectTypeDef(Schema $schema, $path)
239
    {
240
        $typeName = $this->typeName($schema, $path);
241
        $this->processed->attach($schema, $typeName);
242
243
        $head = '';
244
        if (!empty($schema->title)) {
245
            $head .= $schema->title . "\n";
246
        }
247
248
        if (!empty($schema->description)) {
249
            $head .= $schema->description . "\n";
250
        }
251
252
        if ($head !== '') {
253
            $head = "\n" . CodeBuilder::padLines(' * ', trim($head), false);
254
        }
255
256
        $res = <<<JSDOC
257
/**$head
258
 * @typedef {$typeName}
259
 * @type {object}
260
261
JSDOC;
262
        if (!empty($schema->properties)) {
263
            foreach ($schema->properties as $propertyName => $propertySchema) {
264
                $typeString = $this->getTypeString($propertySchema, $path . '/' . $propertyName);
265
                $res .= <<<JSDOC
266
 * @property {{$typeString}} {$propertyName}{$this->description($propertySchema)}.
267
268
JSDOC;
269
270
            }
271
        }
272
273
        $res .= <<<JSDOC
274
 */
275
276
277
JSDOC;
278
279
        $this->file .= $res;
280
281
        return $typeName;
282
    }
283
284
    private function description(Schema $schema)
285
    {
286
        $res = str_replace("\n", " ", $schema->title . $schema->description);
287
        if ($res) {
288
            return ' - ' . rtrim($res, '.');
289
        }
290
291
        return '';
292
    }
293
}