Passed
Pull Request — master (#411)
by Def
07:49 queued 04:41
created

ColumnSchemaBuilder::defaultValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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