TypeBuilder   F
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 163
c 2
b 0
f 0
dl 0
loc 295
rs 3.2
wmc 65

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
F getTypeString() 0 194 50
A typeName() 0 13 4
B makeObjectTypeDef() 0 44 6
A description() 0 15 4

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;
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
            if ($schema->format === 'binary') {
197
                $or[] = 'File';
198
                $or[] = 'Blob';
199
            } else {
200
                $or[] = 'String';
201
            }
202
        }
203
204
        if ($isNumber) {
205
            $or [] = 'Number';
206
        }
207
208
        if ($isBoolean) {
209
            $or [] = 'Boolean';
210
        }
211
212
        $res = '';
213
        foreach ($or as $item) {
214
            if (!empty($item) && $item !== '*') {
215
                $res .= '|' . ($isOptional ? '?' : '') . $item;
216
            }
217
        }
218
219
        if ($res !== '') {
220
            $res = substr($res, 1);
221
        } else {
222
            $res = '*';
223
        }
224
225
        return $res;
226
    }
227
228
    private function typeName(Schema $schema, $path)
229
    {
230
        if ($fromRefs = $schema->getFromRefs()) {
231
            $path = $fromRefs[count($fromRefs) - 1];
232
        }
233
234
        foreach ($this->trimNamePrefix as $prefix) {
235
            if ($prefix === substr($path, 0, strlen($prefix))) {
236
                $path = substr($path, strlen($prefix));
237
            }
238
        }
239
240
        return PhpCode::makePhpName($this->addNamePrefix . '_' . $path, false);
241
    }
242
243
    private function makeObjectTypeDef(Schema $schema, $path)
244
    {
245
        $typeName = $this->typeName($schema, $path);
246
        $this->processed->attach($schema, $typeName);
247
248
        $head = '';
249
        if (!empty($schema->title)) {
250
            $head .= $schema->title . "\n";
251
        }
252
253
        if (!empty($schema->description)) {
254
            $head .= $schema->description . "\n";
255
        }
256
257
        if ($head !== '') {
258
            $head = "\n" . CodeBuilder::padLines(' * ', trim($head), false);
259
        }
260
261
        $res = <<<JSDOC
262
/**$head
263
 * @typedef {$typeName}
264
 * @type {Object}
265
266
JSDOC;
267
        if (!empty($schema->properties)) {
268
            foreach ($schema->properties as $propertyName => $propertySchema) {
269
                $typeString = $this->getTypeString($propertySchema, $path . '/' . $propertyName);
270
                $res .= <<<JSDOC
271
 * @property {{$typeString}} {$propertyName}{$this->description($propertySchema)}
272
273
JSDOC;
274
275
            }
276
        }
277
278
        $res .= <<<JSDOC
279
 */
280
281
282
JSDOC;
283
284
        $this->file .= $res;
285
286
        return $typeName;
287
    }
288
289
    private function description(Schema $schema)
290
    {
291
        $res = str_replace("\n", " ", trim($schema->title));
292
        if (trim($schema->description)) {
293
            if ($res) {
294
                $res .= ". ";
295
            }
296
297
            $res .= str_replace("\n", " ", trim($schema->description));
298
        }
299
        if ($res) {
300
            return ' - ' . rtrim($res, '.') . '.';
301
        }
302
303
        return '';
304
    }
305
}