Passed
Push — master ( d211ac...d5e287 )
by Def
02:10
created

AbstractColumnSchemaBuilder   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 351
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 46
eloc 113
c 1
b 0
f 0
dl 0
loc 351
rs 8.72

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A buildLengthString() 0 11 3
A buildUniqueString() 0 3 2
A buildNotNullString() 0 11 3
A defaultExpression() 0 5 1
A check() 0 5 1
A null() 0 5 1
A unique() 0 5 1
A comment() 0 5 1
A notNull() 0 5 1
A defaultValue() 0 9 2
A unsigned() 0 11 1
A append() 0 5 1
A setFormat() 0 3 1
A asString() 0 9 2
A getTypeCategory() 0 3 1
A buildDefaultValue() 0 11 4
A isUnsigned() 0 3 1
A getAppend() 0 3 1
A buildAppendString() 0 3 2
A buildCheckString() 0 3 2
A buildDefaultString() 0 8 2
A getCategoryMap() 0 3 1
A isUnique() 0 3 1
A buildCommentString() 0 3 1
A getDefault() 0 3 1
A getComment() 0 3 1
A buildUnsignedString() 0 3 1
A getType() 0 3 1
A buildCompleteString() 0 15 1
A getCheck() 0 3 1
A isNotNull() 0 3 1
A getLength() 0 3 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Schema;
6
7
use Yiisoft\Db\Expression\Expression;
8
use Yiisoft\Db\Helper\StringHelper;
9
10
use function gettype;
11
use function strtr;
12
13
/**
14
 * The AbstractColumnSchemaBuilder class is a utility class that provides a convenient way to create column schemas for
15
 * use with Schema class @see Schema.
16
 *
17
 * It provides methods for specifying the properties of a column, such as its type, size, default value, and whether it
18
 * is nullable or not. It also provides a method for creating a column schema based on the specified properties.
19
 *
20
 * For example, the following code creates a column schema for an integer column:
21
 *
22
 * ```php
23
 * $column = (new ColumnSchemaBuilder(Schema::TYPE_INTEGER))->notNull()->defaultValue(0);
24
 * ```
25
 *
26
 * The AbstractColumnSchemaBuilder class provides a fluent interface, which means that the methods can be chained
27
 * together to create a column schema with multiple properties in a single line of code.
28
 */
29
abstract class AbstractColumnSchemaBuilder implements ColumnSchemaBuilderInterface
30
{
31
    /**
32
     * Internally used constants representing categories that abstract column types fall under.
33
     *
34
     * {@see $categoryMap} for mappings of abstract column types to category.
35
     */
36
    public const CATEGORY_PK = 'pk';
37
    public const CATEGORY_STRING = 'string';
38
    public const CATEGORY_NUMERIC = 'numeric';
39
    public const CATEGORY_TIME = 'time';
40
    public const CATEGORY_OTHER = 'other';
41
42
    protected bool|null $isNotNull = null;
43
    protected bool $isUnique = false;
44
    protected string|null $check = null;
45
    protected mixed $default = null;
46
    protected string|null $append = null;
47
    protected bool $isUnsigned = false;
48
    protected string|null $comment = null;
49
    protected string $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}';
50
51
    /** @psalm-var string[] */
52
    private array $categoryMap = [
53
        SchemaInterface::TYPE_PK => self::CATEGORY_PK,
54
        SchemaInterface::TYPE_UPK => self::CATEGORY_PK,
55
        SchemaInterface::TYPE_BIGPK => self::CATEGORY_PK,
56
        SchemaInterface::TYPE_UBIGPK => self::CATEGORY_PK,
57
        SchemaInterface::TYPE_CHAR => self::CATEGORY_STRING,
58
        SchemaInterface::TYPE_STRING => self::CATEGORY_STRING,
59
        SchemaInterface::TYPE_TEXT => self::CATEGORY_STRING,
60
        SchemaInterface::TYPE_TINYINT => self::CATEGORY_NUMERIC,
61
        SchemaInterface::TYPE_SMALLINT => self::CATEGORY_NUMERIC,
62
        SchemaInterface::TYPE_INTEGER => self::CATEGORY_NUMERIC,
63
        SchemaInterface::TYPE_BIGINT => self::CATEGORY_NUMERIC,
64
        SchemaInterface::TYPE_FLOAT => self::CATEGORY_NUMERIC,
65
        SchemaInterface::TYPE_DOUBLE => self::CATEGORY_NUMERIC,
66
        SchemaInterface::TYPE_DECIMAL => self::CATEGORY_NUMERIC,
67
        SchemaInterface::TYPE_DATETIME => self::CATEGORY_TIME,
68
        SchemaInterface::TYPE_TIMESTAMP => self::CATEGORY_TIME,
69
        SchemaInterface::TYPE_TIME => self::CATEGORY_TIME,
70
        SchemaInterface::TYPE_DATE => self::CATEGORY_TIME,
71
        SchemaInterface::TYPE_BINARY => self::CATEGORY_OTHER,
72
        SchemaInterface::TYPE_BOOLEAN => self::CATEGORY_NUMERIC,
73
        SchemaInterface::TYPE_MONEY => self::CATEGORY_NUMERIC,
74
    ];
75
76
    /**
77
     * @psalm-param string[]|int[]|int|string|null $length
78
     */
79
    public function __construct(
80
        protected string $type,
81
        protected int|string|array|null $length = null
82
    ) {
83
    }
84
85
    public function notNull(): static
86
    {
87
        $this->isNotNull = true;
88
89
        return $this;
90
    }
91
92
    public function null(): static
93
    {
94
        $this->isNotNull = false;
95
96
        return $this;
97
    }
98
99
    public function unique(): static
100
    {
101
        $this->isUnique = true;
102
103
        return $this;
104
    }
105
106
    public function check(string|null $check): static
107
    {
108
        $this->check = $check;
109
110
        return $this;
111
    }
112
113
    public function defaultValue(mixed $default): static
114
    {
115
        if ($default === null) {
116
            $this->null();
117
        }
118
119
        $this->default = $default;
120
121
        return $this;
122
    }
123
124
    public function comment(string|null $comment): static
125
    {
126
        $this->comment = $comment;
127
128
        return $this;
129
    }
130
131
    /**
132
     * Marks column as unsigned.
133
     *
134
     * @return static The column schema builder instance itself.
135
     */
136
    public function unsigned(): static
137
    {
138
        $this->type = match ($this->type) {
139
            SchemaInterface::TYPE_PK => SchemaInterface::TYPE_UPK,
140
            SchemaInterface::TYPE_BIGPK => SchemaInterface::TYPE_UBIGPK,
141
            default => $this->type,
142
        };
143
144
        $this->isUnsigned = true;
145
146
        return $this;
147
    }
148
149
    public function defaultExpression(string $default): static
150
    {
151
        $this->default = new Expression($default);
152
153
        return $this;
154
    }
155
156
    public function setFormat(string $format): void
157
    {
158
        $this->format = $format;
159
    }
160
161
    public function append(string $sql): static
162
    {
163
        $this->append = $sql;
164
165
        return $this;
166
    }
167
168
    public function asString(): string
169
    {
170
        if ($this->getTypeCategory() === self::CATEGORY_PK) {
171
            $format = '{type}{check}{comment}{append}';
172
        } else {
173
            $format = $this->format;
174
        }
175
176
        return $this->buildCompleteString($format);
177
    }
178
179
    /**
180
     * Builds the length, precision part of the column.
181
     *
182
     * @return string A string containing the length/precision of the column.
183
     */
184
    protected function buildLengthString(): string
185
    {
186
        if (empty($this->length)) {
187
            return '';
188
        }
189
190
        if (is_array($this->length)) {
191
            $this->length = implode(',', $this->length);
192
        }
193
194
        return '(' . $this->length . ')';
0 ignored issues
show
Bug introduced by
Are you sure $this->length of type array|integer|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

194
        return '(' . /** @scrutinizer ignore-type */ $this->length . ')';
Loading history...
195
    }
196
197
    /**
198
     * Builds the not null constraint for the column.
199
     *
200
     * @return string A string 'NOT NULL' if {@see isNotNull} is true, 'NULL' if {@see isNotNull} is false or an empty
201
     * string otherwise.
202
     */
203
    protected function buildNotNullString(): string
204
    {
205
        if ($this->isNotNull === true) {
206
            return ' NOT NULL';
207
        }
208
209
        if ($this->isNotNull === false) {
210
            return ' NULL';
211
        }
212
213
        return '';
214
    }
215
216
    /**
217
     * Builds the unique constraint for the column.
218
     *
219
     * @return string A string 'UNIQUE' if {@see isUnique} is true, otherwise it returns an empty string.
220
     */
221
    protected function buildUniqueString(): string
222
    {
223
        return $this->isUnique ? ' UNIQUE' : '';
224
    }
225
226
    /**
227
     * Return the default value for the column.
228
     *
229
     * @return string|null string with default value of column.
230
     */
231
    protected function buildDefaultValue()
232
    {
233
        if ($this->default === null) {
234
            return $this->isNotNull === false ? 'NULL' : null;
235
        }
236
237
        return match (gettype($this->default)) {
238
            'object', 'integer' => (string)$this->default,
239
            'double' => StringHelper::normalizeFloat((string)$this->default),
240
            'boolean' => $this->default ? 'TRUE' : 'FALSE',
241
            default => "'$this->default'",
242
        };
243
    }
244
245
    /**
246
     * Builds the default value specification for the column.
247
     *
248
     * @return string A string containing the DEFAULT keyword and the default value.
249
     */
250
    protected function buildDefaultString(): string
251
    {
252
        $defaultValue = $this->buildDefaultValue();
253
        if ($defaultValue === null) {
254
            return '';
255
        }
256
257
        return ' DEFAULT ' . $defaultValue;
258
    }
259
260
    /**
261
     * Builds the check constraint for the column.
262
     *
263
     * @return string A string containing the CHECK constraint.
264
     */
265
    protected function buildCheckString(): string
266
    {
267
        return !empty($this->check) ? " CHECK ($this->check)" : '';
268
    }
269
270
    /**
271
     * Builds the unsigned string for column. Defaults to unsupported.
272
     *
273
     * @return string A string containing the UNSIGNED keyword.
274
     */
275
    protected function buildUnsignedString(): string
276
    {
277
        return '';
278
    }
279
280
    /**
281
     * Builds the custom string that's appended to column definition.
282
     *
283
     * @return string A string containing the custom SQL fragment appended to column definition.
284
     */
285
    protected function buildAppendString(): string
286
    {
287
        return !empty($this->append) ? ' ' . $this->append : '';
288
    }
289
290
    /**
291
     * @return string|null A string containing the column type category name.
292
     */
293
    protected function getTypeCategory(): string|null
294
    {
295
        return $this->categoryMap[$this->type] ?? null;
296
    }
297
298
    /**
299
     * Builds the comment specification for the column.
300
     *
301
     * @return string A string containing the COMMENT keyword and the comment itself.
302
     */
303
    protected function buildCommentString(): string
304
    {
305
        return '';
306
    }
307
308
    /**
309
     * Returns the complete column definition from input format.
310
     *
311
     * @param string $format The format of the definition.
312
     *
313
     * @return string A string containing the complete column definition.
314
     */
315
    protected function buildCompleteString(string $format): string
316
    {
317
        $placeholderValues = [
318
            '{type}' => $this->type,
319
            '{length}' => $this->buildLengthString(),
320
            '{unsigned}' => $this->buildUnsignedString(),
321
            '{notnull}' => $this->buildNotNullString(),
322
            '{unique}' => $this->buildUniqueString(),
323
            '{default}' => $this->buildDefaultString(),
324
            '{check}' => $this->buildCheckString(),
325
            '{comment}' => $this->buildCommentString(),
326
            '{append}' => $this->buildAppendString(),
327
        ];
328
329
        return strtr($format, $placeholderValues);
330
    }
331
332
    public function getType(): string|null
333
    {
334
        return $this->type;
335
    }
336
337
    public function getLength(): array|int|string|null
338
    {
339
        return $this->length;
340
    }
341
342
    public function isNotNull(): bool|null
343
    {
344
        return $this->isNotNull;
345
    }
346
347
    public function isUnique(): bool
348
    {
349
        return $this->isUnique;
350
    }
351
352
    public function getCheck(): string|null
353
    {
354
        return $this->check;
355
    }
356
357
    public function getDefault(): mixed
358
    {
359
        return $this->default;
360
    }
361
362
    public function getAppend(): string|null
363
    {
364
        return $this->append;
365
    }
366
367
    public function isUnsigned(): bool
368
    {
369
        return $this->isUnsigned;
370
    }
371
372
    public function getCategoryMap(): array
373
    {
374
        return $this->categoryMap;
375
    }
376
377
    public function getComment(): string|null
378
    {
379
        return $this->comment;
380
    }
381
}
382