Passed
Pull Request — master (#215)
by Wilmer
13:47
created

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