Passed
Pull Request — master (#678)
by Wilmer
09:49 queued 07:20
created

AbstractColumn::buildPrimaryKeyString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Schema\Builder;
6
7
use Yiisoft\Db\Expression\Expression;
8
use Yiisoft\Db\Helper\DbStringHelper;
9
use Yiisoft\Db\Schema\SchemaInterface;
10
11
use function gettype;
12
use function implode;
13
use function strtr;
14
15
/**
16
 * Is a utility class that provides a convenient way to create column schemas for {@see AbstractSchema}.
17
 *
18
 * It provides methods for specifying the properties of a column, such as its type, size, default value, and whether it
19
 * is nullable or not. It also provides a method for creating a column schema based on the specified properties.
20
 *
21
 * For example, the following code creates a column schema for an integer column:
22
 *
23
 * ```php
24
 * $column = (new Column(SchemaInterface::TYPE_INTEGER))->notNull()->defaultValue(0);
25
 * ```
26
 *
27
 * Provides a fluent interface, which means that the methods can be chained together to create a column schema with
28
 * many properties in a single line of code.
29
 */
30
abstract class AbstractColumn implements ColumnInterface
31
{
32
    /**
33
     * Allows you to group and define the abstract column type as primary key.
34
     */
35
    public const TYPE_CATEGORY_PK = 'pk';
36
    /**
37
     * Allows you to group and define the abstract column type as `string`.
38
     */
39
    public const TYPE_CATEGORY_STRING = 'string';
40
    /**
41
     * Allows you to group and define the abstract column type as `numeric`.
42
     */
43
    public const TYPE_CATEGORY_NUMERIC = 'numeric';
44
    /**
45
     * Allows you to group and define the abstract column type as `time`.
46
     */
47
    public const TYPE_CATEGORY_TIME = 'time';
48
    /**
49
     * Allows you to group and define the abstract column type as `other`.
50
     */
51
    public const TYPE_CATEGORY_OTHER = 'other';
52
    /**
53
     * Allows you to group and define the abstract column type as `uuid`.
54
     */
55
    public const TYPE_CATEGORY_UUID = 'uuid';
56
    /**
57
     * Allows you to group and define the abstract column type as `uuid` primary key.
58
     */
59
    public const TYPE_CATEGORY_UUID_PK = 'uuid_pk';
60
61
    protected bool|null $isNotNull = null;
62
    protected bool $isUnique = false;
63
    protected bool $isPrimaryKey = false;
64
    protected string|null $check = null;
65
    protected mixed $default = null;
66
    protected string|null $append = null;
67
    protected bool $isUnsigned = false;
68
    protected string|null $comment = null;
69
    protected string $format = '{type}{length}{notnull}{primarykey}{unique}{default}{check}{comment}{append}';
70
71
    /** @psalm-var string[] */
72
    private array $categoryMap = [
73
        SchemaInterface::TYPE_PK => self::TYPE_CATEGORY_PK,
74
        SchemaInterface::TYPE_UPK => self::TYPE_CATEGORY_PK,
75
        SchemaInterface::TYPE_BIGPK => self::TYPE_CATEGORY_PK,
76
        SchemaInterface::TYPE_UBIGPK => self::TYPE_CATEGORY_PK,
77
        SchemaInterface::TYPE_CHAR => self::TYPE_CATEGORY_STRING,
78
        SchemaInterface::TYPE_STRING => self::TYPE_CATEGORY_STRING,
79
        SchemaInterface::TYPE_TEXT => self::TYPE_CATEGORY_STRING,
80
        SchemaInterface::TYPE_TINYINT => self::TYPE_CATEGORY_NUMERIC,
81
        SchemaInterface::TYPE_SMALLINT => self::TYPE_CATEGORY_NUMERIC,
82
        SchemaInterface::TYPE_INTEGER => self::TYPE_CATEGORY_NUMERIC,
83
        SchemaInterface::TYPE_BIGINT => self::TYPE_CATEGORY_NUMERIC,
84
        SchemaInterface::TYPE_FLOAT => self::TYPE_CATEGORY_NUMERIC,
85
        SchemaInterface::TYPE_DOUBLE => self::TYPE_CATEGORY_NUMERIC,
86
        SchemaInterface::TYPE_DECIMAL => self::TYPE_CATEGORY_NUMERIC,
87
        SchemaInterface::TYPE_DATETIME => self::TYPE_CATEGORY_TIME,
88
        SchemaInterface::TYPE_TIMESTAMP => self::TYPE_CATEGORY_TIME,
89
        SchemaInterface::TYPE_TIME => self::TYPE_CATEGORY_TIME,
90
        SchemaInterface::TYPE_DATE => self::TYPE_CATEGORY_TIME,
91
        SchemaInterface::TYPE_BINARY => self::TYPE_CATEGORY_OTHER,
92
        SchemaInterface::TYPE_BOOLEAN => self::TYPE_CATEGORY_NUMERIC,
93
        SchemaInterface::TYPE_MONEY => self::TYPE_CATEGORY_NUMERIC,
94
        SchemaInterface::TYPE_UUID => self::TYPE_CATEGORY_UUID,
95
        SchemaInterface::TYPE_UUID_PK => self::TYPE_CATEGORY_UUID_PK,
96
    ];
97
98
    /**
99
     * @psalm-param string[]|int[]|int|string|null $length
100
     */
101
    public function __construct(
102
        protected string|null $type = null,
103
        protected int|string|array|null $length = null
104
    ) {
105
    }
106
107
    public function notNull(): static
108
    {
109
        $this->isNotNull = true;
110
        return $this;
111
    }
112
113
    public function null(): static
114
    {
115
        $this->isNotNull = false;
116
        return $this;
117
    }
118
119
    public function primaryKey(): static
120
    {
121
        $this->isPrimaryKey = true;
122
        return $this;
123
    }
124
125
    public function unique(): static
126
    {
127
        $this->isUnique = true;
128
        return $this;
129
    }
130
131
    public function check(string|null $check): static
132
    {
133
        $this->check = $check;
134
        return $this;
135
    }
136
137
    public function defaultValue(mixed $default): static
138
    {
139
        if ($default === null) {
140
            $this->null();
141
        }
142
143
        $this->default = $default;
144
145
        return $this;
146
    }
147
148
    public function comment(string|null $comment): static
149
    {
150
        $this->comment = $comment;
151
        return $this;
152
    }
153
154
    /**
155
     * Marks column as unsigned.
156
     */
157
    public function unsigned(): static
158
    {
159
        $this->type = match ($this->type) {
160
            SchemaInterface::TYPE_PK => SchemaInterface::TYPE_UPK,
161
            SchemaInterface::TYPE_BIGPK => SchemaInterface::TYPE_UBIGPK,
162
            default => $this->type,
163
        };
164
165
        $this->isUnsigned = true;
166
167
        return $this;
168
    }
169
170
    public function defaultExpression(string $default): static
171
    {
172
        $this->default = new Expression($default);
173
        return $this;
174
    }
175
176
    public function setFormat(string $format): void
177
    {
178
        $this->format = $format;
179
    }
180
181
    public function append(string $sql): static
182
    {
183
        $this->append = $sql;
184
        return $this;
185
    }
186
187
    public function buildString(): string
188
    {
189
        $format = match ($this->getTypeCategory()) {
190
            self::TYPE_CATEGORY_PK => '{type}{check}{comment}{append}',
191
            self::TYPE_CATEGORY_UUID => '{type}{notnull}{unique}{default}{check}{comment}{append}',
192
            self::TYPE_CATEGORY_UUID_PK => '{type}{notnull}{default}{check}{comment}{append}',
193
            default => $this->format,
194
        };
195
196
        return $this->buildCompleteString($format);
197
    }
198
199
    public function getType(): string|null
200
    {
201
        return $this->type;
202
    }
203
204
    public function getLength(): array|int|string|null
205
    {
206
        return $this->length;
207
    }
208
209
    public function isNotNull(): bool|null
210
    {
211
        return $this->isNotNull;
212
    }
213
214
    public function isPrimaryKey(): bool
215
    {
216
        return $this->isPrimaryKey;
217
    }
218
219
    public function isUnique(): bool
220
    {
221
        return $this->isUnique;
222
    }
223
224
    public function getCheck(): string|null
225
    {
226
        return $this->check;
227
    }
228
229
    public function getDefault(): mixed
230
    {
231
        return $this->default;
232
    }
233
234
    public function getAppend(): string|null
235
    {
236
        return $this->append;
237
    }
238
239
    public function isUnsigned(): bool
240
    {
241
        return $this->isUnsigned;
242
    }
243
244
    public function getCategoryMap(): array
245
    {
246
        return $this->categoryMap;
247
    }
248
249
    public function getComment(): string|null
250
    {
251
        return $this->comment;
252
    }
253
254
    /**
255
     * Builds the length, precision part of the column.
256
     *
257
     * @return string A string containing the length/precision of the column.
258
     */
259
    protected function buildLengthString(): string
260
    {
261
        if (empty($this->length)) {
262
            return '';
263
        }
264
265
        if (is_array($this->length)) {
266
            $this->length = implode(',', $this->length);
267
        }
268
269
        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

269
        return '(' . /** @scrutinizer ignore-type */ $this->length . ')';
Loading history...
270
    }
271
272
    /**
273
     * Builds the not null constraint for the column.
274
     *
275
     * @return string A string 'NOT NULL' if {@see isNotNull} is true, 'NULL' if {@see isNotNull} is false or an empty
276
     * string otherwise.
277
     */
278
    protected function buildNotNullString(): string
279
    {
280
        if ($this->isNotNull === true) {
281
            return ' NOT NULL';
282
        }
283
284
        if ($this->isNotNull === false) {
285
            return ' NULL';
286
        }
287
288
        return '';
289
    }
290
291
    /**
292
     * Builds the primary key constraint for the column.
293
     */
294
    protected function buildPrimaryKeyString(): string
295
    {
296
        return $this->isPrimaryKey ? ' PRIMARY KEY' : '';
297
    }
298
299
    /**
300
     * Builds the unique constraint for the column.
301
     *
302
     * @return string A string 'UNIQUE' if {@see isUnique} is true, otherwise it returns an empty string.
303
     */
304
    protected function buildUniqueString(): string
305
    {
306
        return $this->isUnique ? ' UNIQUE' : '';
307
    }
308
309
    /**
310
     * Return the default value for the column.
311
     *
312
     * @return string|null string with default value of column.
313
     */
314
    protected function buildDefaultValue(): string|null
315
    {
316
        if ($this->default === null) {
317
            return $this->isNotNull === false ? 'NULL' : null;
318
        }
319
320
        return match (gettype($this->default)) {
321
            'object', 'integer' => (string) $this->default,
322
            'double' => DbStringHelper::normalizeFloat((string) $this->default),
323
            'boolean' => $this->default ? 'TRUE' : 'FALSE',
324
            default => "'$this->default'",
325
        };
326
    }
327
328
    /**
329
     * Builds the default value specification for the column.
330
     *
331
     * @return string A string containing the DEFAULT keyword and the default value.
332
     */
333
    protected function buildDefaultString(): string
334
    {
335
        $defaultValue = $this->buildDefaultValue();
336
        if ($defaultValue === null) {
337
            return '';
338
        }
339
340
        return ' DEFAULT ' . $defaultValue;
341
    }
342
343
    /**
344
     * Builds the check constraint for the column.
345
     *
346
     * @return string A string containing the CHECK constraint.
347
     */
348
    protected function buildCheckString(): string
349
    {
350
        return !empty($this->check) ? " CHECK ($this->check)" : '';
351
    }
352
353
    /**
354
     * Builds the unsigned string for column. Defaults to unsupported.
355
     *
356
     * @return string A string containing the UNSIGNED keyword.
357
     */
358
    protected function buildUnsignedString(): string
359
    {
360
        return '';
361
    }
362
363
    /**
364
     * Builds the custom string that's appended to column definition.
365
     *
366
     * @return string A string containing the custom SQL fragment appended to column definition.
367
     */
368
    protected function buildAppendString(): string
369
    {
370
        return !empty($this->append) ? ' ' . $this->append : '';
371
    }
372
373
    /**
374
     * @return string|null A string containing the column type category name.
375
     */
376
    protected function getTypeCategory(): string|null
377
    {
378
        return $this->categoryMap[$this->type] ?? null;
379
    }
380
381
    /**
382
     * Builds the comment specification for the column.
383
     *
384
     * @return string A string containing the COMMENT keyword and the comment itself.
385
     */
386
    protected function buildCommentString(): string
387
    {
388
        return '';
389
    }
390
391
    /**
392
     * Returns the complete column definition from input format.
393
     *
394
     * @param string $format The format of the definition.
395
     *
396
     * @return string A string containing the complete column definition.
397
     */
398
    protected function buildCompleteString(string $format): string
399
    {
400
        $placeholderValues = [
401
            '{type}' => $this->type,
402
            '{length}' => $this->buildLengthString(),
403
            '{unsigned}' => $this->buildUnsignedString(),
404
            '{notnull}' => $this->buildNotNullString(),
405
            '{primarykey}' => $this->buildPrimaryKeyString(),
406
            '{unique}' => $this->buildUniqueString(),
407
            '{default}' => $this->buildDefaultString(),
408
            '{check}' => $this->buildCheckString(),
409
            '{comment}' => $this->buildCommentString(),
410
            '{append}' => $this->buildAppendString(),
411
        ];
412
413
        return strtr($format, $placeholderValues);
414
    }
415
}
416