Passed
Pull Request — master (#467)
by Alexander
10:48 queued 08:17
created

AbstractColumnSchemaBuilder::buildCompleteString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 15
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Schema;
6
7
use Stringable;
8
use Yiisoft\Db\Expression\Expression;
9
use Yiisoft\Strings\NumericHelper;
10
11
use function gettype;
12
use function strtr;
13
14
/**
15
 * The ColumnSchemaBuilder class is a utility class that provides a convenient way to create column schemas for use
16
 * with Schema class @see Schema.
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 ColumnSchemaBuilder(Schema::TYPE_INTEGER))->notNull()->defaultValue(0);
25
 * ```
26
 *
27
 * The ColumnSchemaBuilder class provides a fluent interface, which means that the methods can be chained together to
28
 * create a column schema with multiple properties in a single line of code.
29
 */
30
abstract class AbstractColumnSchemaBuilder implements Stringable
31
{
32
    /**
33
     * Internally used constants representing categories that abstract column types fall under.
34
     *
35
     * {@see $categoryMap} for mappings of abstract column types to category.
36
     */
37
    public const CATEGORY_PK = 'pk';
38
    public const CATEGORY_STRING = 'string';
39
    public const CATEGORY_NUMERIC = 'numeric';
40
    public const CATEGORY_TIME = 'time';
41
    public const CATEGORY_OTHER = 'other';
42
43
    protected bool|null $isNotNull = null;
44
    protected bool $isUnique = false;
45
    protected string|null $check = null;
46
    protected mixed $default = null;
47
    protected string|null $append = null;
48
    protected bool $isUnsigned = false;
49
    protected string|null $comment = null;
50
51
    /** @psalm-var string[] */
52
    private array $categoryMap = [
53
        AbstractSchema::TYPE_PK => self::CATEGORY_PK,
54
        AbstractSchema::TYPE_UPK => self::CATEGORY_PK,
55
        AbstractSchema::TYPE_BIGPK => self::CATEGORY_PK,
56
        AbstractSchema::TYPE_UBIGPK => self::CATEGORY_PK,
57
        AbstractSchema::TYPE_CHAR => self::CATEGORY_STRING,
58
        AbstractSchema::TYPE_STRING => self::CATEGORY_STRING,
59
        AbstractSchema::TYPE_TEXT => self::CATEGORY_STRING,
60
        AbstractSchema::TYPE_TINYINT => self::CATEGORY_NUMERIC,
61
        AbstractSchema::TYPE_SMALLINT => self::CATEGORY_NUMERIC,
62
        AbstractSchema::TYPE_INTEGER => self::CATEGORY_NUMERIC,
63
        AbstractSchema::TYPE_BIGINT => self::CATEGORY_NUMERIC,
64
        AbstractSchema::TYPE_FLOAT => self::CATEGORY_NUMERIC,
65
        AbstractSchema::TYPE_DOUBLE => self::CATEGORY_NUMERIC,
66
        AbstractSchema::TYPE_DECIMAL => self::CATEGORY_NUMERIC,
67
        AbstractSchema::TYPE_DATETIME => self::CATEGORY_TIME,
68
        AbstractSchema::TYPE_TIMESTAMP => self::CATEGORY_TIME,
69
        AbstractSchema::TYPE_TIME => self::CATEGORY_TIME,
70
        AbstractSchema::TYPE_DATE => self::CATEGORY_TIME,
71
        AbstractSchema::TYPE_BINARY => self::CATEGORY_OTHER,
72
        AbstractSchema::TYPE_BOOLEAN => self::CATEGORY_NUMERIC,
73
        AbstractSchema::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
    /**
86
     * Adds a `NOT NULL` constraint to the column.
87
     *
88
     * @return static The column schema builder instance itself.
89
     *
90
     * @see isNotNull
91
     */
92
    public function notNull(): static
93
    {
94
        $this->isNotNull = true;
95
96
        return $this;
97
    }
98
99
    /**
100
     * Adds a `NULL` constraint to the column.
101
     *
102
     * @return static The column schema builder instance itself.
103
     *
104
     * @see isNotNull
105
     */
106
    public function null(): static
107
    {
108
        $this->isNotNull = false;
109
110
        return $this;
111
    }
112
113
    /**
114
     * Adds a `UNIQUE` constraint to the column.
115
     *
116
     * @return static The column schema builder instance itself.
117
     *
118
     * @see isUnique
119
     */
120
    public function unique(): static
121
    {
122
        $this->isUnique = true;
123
124
        return $this;
125
    }
126
127
    /**
128
     * Specify a `CHECK` constraint for the column.
129
     *
130
     * @param string|null $check The SQL of the `CHECK` constraint to be added.
131
     *
132
     * @return static The column schema builder instance itself.
133
     */
134
    public function check(string|null $check): static
135
    {
136
        $this->check = $check;
137
138
        return $this;
139
    }
140
141
    /**
142
     * Specify the default value for the column.
143
     *
144
     * @param mixed $default The default value to be used.
145
     *
146
     * @return static The column schema builder instance itself.
147
     */
148
    public function defaultValue(mixed $default): static
149
    {
150
        if ($default === null) {
151
            $this->null();
152
        }
153
154
        $this->default = $default;
155
        return $this;
156
    }
157
158
    /**
159
     * Specifies the comment for column.
160
     *
161
     * @param string|null $comment The comment to be added.
162
     *
163
     * @return static The column schema builder instance itself.
164
     */
165
    public function comment(string|null $comment): static
166
    {
167
        $this->comment = $comment;
168
169
        return $this;
170
    }
171
172
    /**
173
     * Marks column as unsigned.
174
     *
175
     * @return static The column schema builder instance itself.
176
     */
177
    public function unsigned(): static
178
    {
179
        $this->type = match ($this->type) {
180
            AbstractSchema::TYPE_PK => AbstractSchema::TYPE_UPK,
181
            AbstractSchema::TYPE_BIGPK => AbstractSchema::TYPE_UBIGPK,
182
            default => $this->type,
183
        };
184
185
        $this->isUnsigned = true;
186
187
        return $this;
188
    }
189
190
    /**
191
     * Specify the default SQL expression for the column.
192
     *
193
     * @param string $default The SQL expression to be used as default value.
194
     *
195
     * @return static The column schema builder instance itself.
196
     */
197
    public function defaultExpression(string $default): static
198
    {
199
        $this->default = new Expression($default);
200
201
        return $this;
202
    }
203
204
    /**
205
     * Specify additional SQL to be appended to column definition.
206
     *
207
     * Position modifiers will be appended after column definition in databases that support them.
208
     *
209
     * @param string $sql The SQL string to be appended.
210
     *
211
     * @return static The column schema builder instance itself.
212
     */
213
    public function append(string $sql): static
214
    {
215
        $this->append = $sql;
216
217
        return $this;
218
    }
219
220
    /**
221
     * Builds the full string for the column's schema including type, length, default value, not null and other SQL
222
     * fragment.
223
     *
224
     * @return string The SQL fragment that will be used for creating the column.
225
     */
226
    public function __toString(): string
227
    {
228
        if ($this->getTypeCategory() === self::CATEGORY_PK) {
229
            $format = '{type}{check}{comment}{append}';
230
        } else {
231
            $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}';
232
        }
233
234
        return $this->buildCompleteString($format);
235
    }
236
237
    /**
238
     * Builds the length, precision part of the column.
239
     *
240
     * @return string A string containing the length/precision of the column.
241
     */
242
    protected function buildLengthString(): string
243
    {
244
        if (empty($this->length)) {
245
            return '';
246
        }
247
248
        if (is_array($this->length)) {
249
            $this->length = implode(',', $this->length);
250
        }
251
252
        return "($this->length)";
253
    }
254
255
    /**
256
     * Builds the not null constraint for the column.
257
     *
258
     * @return string A string 'NOT NULL' if {@see isNotNull} is true, 'NULL' if {@see isNotNull} is false or an empty
259
     * string otherwise.
260
     */
261
    protected function buildNotNullString(): string
262
    {
263
        if ($this->isNotNull === true) {
264
            return ' NOT NULL';
265
        }
266
267
        if ($this->isNotNull === false) {
268
            return ' NULL';
269
        }
270
271
        return '';
272
    }
273
274
    /**
275
     * Builds the unique constraint for the column.
276
     *
277
     * @return string A string 'UNIQUE' if {@see isUnique} is true, otherwise it returns an empty string.
278
     */
279
    protected function buildUniqueString(): string
280
    {
281
        return $this->isUnique ? ' UNIQUE' : '';
282
    }
283
284
    /**
285
     * Builds the default value specification for the column.
286
     *
287
     * @return string A string containing the DEFAULT keyword and the default value.
288
     */
289
    protected function buildDefaultString(): string
290
    {
291
        if ($this->default === null) {
292
            return $this->isNotNull === false ? ' DEFAULT NULL' : '';
293
        }
294
295
        $string = ' DEFAULT ';
296
297
        $string .= match (gettype($this->default)) {
298
            'object', 'integer' => (string)$this->default,
299
            'double' => NumericHelper::normalize((string)$this->default),
300
            'boolean' => $this->default ? 'TRUE' : 'FALSE',
301
            default => "'$this->default'",
302
        };
303
304
        return $string;
305
    }
306
307
    /**
308
     * Builds the check constraint for the column.
309
     *
310
     * @return string A string containing the CHECK constraint.
311
     */
312
    protected function buildCheckString(): string
313
    {
314
        return !empty($this->check) ? " CHECK ($this->check)" : '';
315
    }
316
317
    /**
318
     * Builds the unsigned string for column. Defaults to unsupported.
319
     *
320
     * @return string A string containing the UNSIGNED keyword.
321
     */
322
    protected function buildUnsignedString(): string
323
    {
324
        return '';
325
    }
326
327
    /**
328
     * Builds the custom string that's appended to column definition.
329
     *
330
     * @return string A string containing the custom SQL fragment appended to column definition.
331
     */
332
    protected function buildAppendString(): string
333
    {
334
        return !empty($this->append) ? ' ' . $this->append : '';
335
    }
336
337
    /**
338
     * @return string|null A string containing the column type category name.
339
     */
340
    protected function getTypeCategory(): string|null
341
    {
342
        return $this->categoryMap[$this->type] ?? null;
343
    }
344
345
    /**
346
     * Builds the comment specification for the column.
347
     *
348
     * @return string A string containing the COMMENT keyword and the comment itself.
349
     */
350
    protected function buildCommentString(): string
351
    {
352
        return '';
353
    }
354
355
    /**
356
     * Returns the complete column definition from input format.
357
     *
358
     * @param string $format The format of the definition.
359
     *
360
     * @return string A string containing the complete column definition.
361
     */
362
    protected function buildCompleteString(string $format): string
363
    {
364
        $placeholderValues = [
365
            '{type}' => $this->type,
366
            '{length}' => $this->buildLengthString(),
367
            '{unsigned}' => $this->buildUnsignedString(),
368
            '{notnull}' => $this->buildNotNullString(),
369
            '{unique}' => $this->buildUniqueString(),
370
            '{default}' => $this->buildDefaultString(),
371
            '{check}' => $this->buildCheckString(),
372
            '{comment}' => $this->buildCommentString(),
373
            '{append}' => $this->buildAppendString(),
374
        ];
375
376
        return strtr($format, $placeholderValues);
377
    }
378
379
    /**
380
     * @return string|null The column type definition such as INTEGER, VARCHAR, DATETIME, etc.
381
     */
382
    public function getType(): string|null
383
    {
384
        return $this->type;
385
    }
386
387
    /**
388
     * @return array|int|string|null The column size or precision definition. This is what goes into the parenthesis
389
     * after the column type. This can be either a string, an integer or an array. If it is an array, the array values
390
     * will be joined into a string separated by comma.
391
     */
392
    public function getLength(): array|int|string|null
393
    {
394
        return $this->length;
395
    }
396
397
    /**
398
     * @return bool|null Whether the column is or not nullable. If this is `true`, a `NOT NULL` constraint will be
399
     * added. If this is `false`, a `NULL` constraint will be added.
400
     */
401
    public function isNotNull(): bool|null
402
    {
403
        return $this->isNotNull;
404
    }
405
406
    /**
407
     * @return bool Whether the column values should be unique. If this is `true`, a `UNIQUE` constraint will be added.
408
     */
409
    public function isUnique(): bool
410
    {
411
        return $this->isUnique;
412
    }
413
414
    /**
415
     * @return string|null The `CHECK` constraint for the column.
416
     */
417
    public function getCheck(): string|null
418
    {
419
        return $this->check;
420
    }
421
422
    /**
423
     * @return mixed The default value of the column.
424
     */
425
    public function getDefault(): mixed
426
    {
427
        return $this->default;
428
    }
429
430
    /**
431
     * @return string|null The SQL string to be appended to column schema definition.
432
     */
433
    public function getAppend(): string|null
434
    {
435
        return $this->append;
436
    }
437
438
    /**
439
     * @return bool Whether the column values should be unsigned. If this is `true`, an `UNSIGNED` keyword will be
440
     * added.
441
     */
442
    public function isUnsigned(): bool
443
    {
444
        return $this->isUnsigned;
445
    }
446
447
    /**
448
     * @return array The mapping of abstract column types (keys) to type categories (values).
449
     */
450
    public function getCategoryMap(): array
451
    {
452
        return $this->categoryMap;
453
    }
454
455
    /**
456
     * @return string|null The comment value of the column.
457
     */
458
    public function getComment(): string|null
459
    {
460
        return $this->comment;
461
    }
462
}
463