Passed
Push — master ( be06f7...c027e3 )
by Wilmer
08:58 queued 06:59
created

ColumnSchemaBuilder::getDefault()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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