Passed
Pull Request — master (#427)
by Wilmer
03:10 queued 10s
created

ColumnSchemaBuilder::getIsNotNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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