Completed
Pull Request — master (#36)
by Viacheslav
10:12
created

TypeBuilder   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 136
c 1
b 0
f 0
dl 0
loc 248
rs 8.48
wmc 49

5 Methods

Rating   Name   Duplication   Size   Complexity  
A description() 0 8 2
F getTypeString() 0 158 37
A typeName() 0 13 4
A __construct() 0 3 1
A makeObjectTypeDef() 0 42 5

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