Passed
Push — master ( a8d37b...dabdd0 )
by Wilmer
10:00
created

ColumnSchemaBuilder::getComment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
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 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 38
    public function __construct(string $type, $length = null)
64
    {
65 38
        $this->type = $type;
66 38
        $this->length = $length;
67 38
    }
68
69
    /**
70
     * Adds a `NOT NULL` constraint to the column.
71
     *
72
     * {@see isNotNull}
73
     *
74
     * @return $this
75
     */
76 15
    public function notNull(): self
77
    {
78 15
        $this->isNotNull = true;
79
80 15
        return $this;
81
    }
82
83
    /**
84
     * Adds a `NULL` constraint to the column.
85
     *
86
     * {@see isNotNull}
87
     *
88
     * @return $this
89
     */
90 14
    public function null(): self
91
    {
92 14
        $this->isNotNull = false;
93
94 14
        return $this;
95
    }
96
97
    /**
98
     * Adds a `UNIQUE` constraint to the column.
99
     *
100
     * {@see isUnique}
101
     *
102
     * @return $this
103
     */
104 1
    public function unique(): self
105
    {
106 1
        $this->isUnique = true;
107
108 1
        return $this;
109
    }
110
111
    /**
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 11
    public function check(?string $check): self
119
    {
120 11
        $this->check = $check;
121
122 11
        return $this;
123
    }
124
125
    /**
126
     * Specify the default value for the column.
127
     *
128
     * @param mixed $default the default value.
129
     *
130
     * @return $this
131
     */
132 14
    public function defaultValue($default): self
133
    {
134 14
        if ($default === null) {
135 10
            $this->null();
136
        }
137
138 14
        $this->default = $default;
139
140 14
        return $this;
141
    }
142
143
    /**
144
     * Specifies the comment for column.
145
     *
146
     * @param string|null $comment the comment
147
     *
148
     * @return $this
149
     */
150 16
    public function comment(?string $comment): self
151
    {
152 16
        $this->comment = $comment;
153
154 16
        return $this;
155
    }
156
157
    /**
158
     * Marks column as unsigned.
159
     *
160
     * @return $this
161
     */
162 20
    public function unsigned(): self
163
    {
164 20
        switch ($this->type) {
165
            case Schema::TYPE_PK:
166 10
                $this->type = Schema::TYPE_UPK;
167 10
                break;
168
            case Schema::TYPE_BIGPK:
169 10
                $this->type = Schema::TYPE_UBIGPK;
170 10
                break;
171
        }
172 20
        $this->isUnsigned = true;
173
174 20
        return $this;
175
    }
176
177
    /**
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 10
    public function after(string $after): self
187
    {
188 10
        $this->after = $after;
189
190 10
        return $this;
191
    }
192
193
    /**
194
     * Adds an `FIRST` constraint to the column.
195
     *
196
     * Note: MySQL, Oracle support only.
197
     *
198
     * @return $this
199
     */
200 10
    public function first(): self
201
    {
202 10
        $this->isFirst = true;
203
204 10
        return $this;
205
    }
206
207
    /**
208
     * Specify the default SQL expression for the column.
209
     *
210
     * @param string $default the default value expression.
211
     *
212
     * @return $this
213
     */
214 1
    public function defaultExpression(string $default): self
215
    {
216 1
        $this->default = new Expression($default);
217
218 1
        return $this;
219
    }
220
221
    /**
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 11
    public function append(string $sql): self
231
    {
232 11
        $this->append = $sql;
233
234 11
        return $this;
235
    }
236
237
    /**
238
     * Builds the full string for the column's schema.
239
     *
240
     * @return string
241
     */
242 20
    public function __toString(): string
243
    {
244 20
        if ($this->getTypeCategory() === self::CATEGORY_PK) {
245 2
            $format = '{type}{check}{comment}{append}';
246
        } else {
247 20
            $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}';
248
        }
249
250 20
        return $this->buildCompleteString($format);
251
    }
252
253
    /**
254
     * Builds the length/precision part of the column.
255
     *
256
     * @return string
257
     */
258 33
    protected function buildLengthString(): string
259
    {
260 33
        if ($this->length === null || $this->length === []) {
261 17
            return '';
262
        }
263 22
        if (is_array($this->length)) {
264 5
            $this->length = implode(',', $this->length);
265
        }
266
267 22
        return "({$this->length})";
268
    }
269
270
    /**
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 33
    protected function buildNotNullString(): string
277
    {
278 33
        if ($this->isNotNull === true) {
279 10
            return ' NOT NULL';
280
        }
281
282 29
        if ($this->isNotNull === false) {
283 8
            return ' NULL';
284
        }
285
286 26
        return '';
287
    }
288
289
    /**
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 33
    protected function buildUniqueString(): string
295
    {
296 33
        return $this->isUnique ? ' UNIQUE' : '';
297
    }
298
299
    /**
300
     * Builds the default value specification for the column.
301
     *
302
     * @return string string with default value of column.
303
     */
304 33
    protected function buildDefaultString(): string
305
    {
306 33
        if ($this->default === null) {
307 30
            return $this->isNotNull === false ? ' DEFAULT NULL' : '';
308
        }
309
310 8
        $string = ' DEFAULT ';
311 8
        switch (gettype($this->default)) {
312 8
            case 'object':
313 5
            case 'integer':
314 7
                $string .= $this->default;
315 7
                break;
316 2
            case 'double':
317
                /* ensure type cast always has . as decimal separator in all locales */
318
                $string .= NumericHelper::normalize((string) $this->default);
319
                break;
320 2
            case 'boolean':
321 1
                $string .= $this->default ? 'TRUE' : 'FALSE';
322 1
                break;
323
            default:
324 1
                $string .= "'{$this->default}'";
325
        }
326
327 8
        return $string;
328
    }
329
330
    /**
331
     * Builds the check constraint for the column.
332
     *
333
     * @return string a string containing the CHECK constraint.
334
     */
335 33
    protected function buildCheckString(): string
336
    {
337 33
        return $this->check !== null ? " CHECK ({$this->check})" : '';
338
    }
339
340
    /**
341
     * Builds the unsigned string for column. Defaults to unsupported.
342
     *
343
     * @return string a string containing UNSIGNED keyword.
344
     */
345 20
    protected function buildUnsignedString(): string
346
    {
347 20
        return '';
348
    }
349
350
    /**
351
     * Builds the after constraint for the column. Defaults to unsupported.
352
     *
353
     * @return string a string containing the AFTER constraint.
354
     */
355 27
    protected function buildAfterString(): string
356
    {
357 27
        return '';
358
    }
359
360
    /**
361
     * Builds the first constraint for the column. Defaults to unsupported.
362
     *
363
     * @return string a string containing the FIRST constraint.
364
     */
365 1
    protected function buildFirstString(): string
366
    {
367 1
        return '';
368
    }
369
370
    /**
371
     * Builds the custom string that's appended to column definition.
372
     *
373
     * @return string custom string to append.
374
     */
375 33
    protected function buildAppendString(): string
376
    {
377 33
        return $this->append !== null ? ' ' . $this->append : '';
378
    }
379
380
    /**
381
     * Returns the category of the column type.
382
     *
383
     * @return string|null a string containing the column type category name.
384
     */
385 33
    protected function getTypeCategory(): ?string
386
    {
387 33
        return $this->categoryMap[$this->type] ?? null;
388
    }
389
390
    /**
391
     * Builds the comment specification for the column.
392
     *
393
     * @return string a string containing the COMMENT keyword and the comment itself
394
     */
395 27
    protected function buildCommentString(): string
396
    {
397 27
        return '';
398
    }
399
400
    /**
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 33
    protected function buildCompleteString(string $format): string
408
    {
409
        $placeholderValues = [
410 33
            '{type}' => $this->type,
411 33
            '{length}' => $this->buildLengthString(),
412 33
            '{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
        ];
421
422 33
        return strtr($format, $placeholderValues);
423
    }
424
425
    /**
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 13
    public function isUnsigned(): bool
489
    {
490 13
        return $this->isUnsigned;
491
    }
492
493
    /**
494
     * @return string|null the column after which this column will be added.
495
     */
496 6
    public function getAfter(): ?string
497
    {
498 6
        return $this->after;
499
    }
500
501
    /**
502
     * @return bool whether this column is to be inserted at the beginning of the table.
503
     */
504 1
    public function isFirst(): bool
505
    {
506 1
        return $this->isFirst;
507
    }
508
509
    /**
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 6
    public function getComment(): ?string
521
    {
522 6
        return $this->comment;
523
    }
524
}
525