Passed
Push — master ( 5c4177...b54841 )
by Wilmer
13:29
created

AbstractColumnSchemaBuilder::asString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 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
use function gettype;
11
use function strtr;
12
13
/**
14
 * The AbstractColumnSchemaBuilder class is a utility class that provides a convenient way to create column schemas for
15
 * use with Schema class @see Schema.
16
 *
17
 * It provides methods for specifying the properties of a column, such as its type, size, default value, and whether it
18
 * is nullable or not. It also provides a method for creating a column schema based on the specified properties.
19
 *
20
 * For example, the following code creates a column schema for an integer column:
21
 *
22
 * ```php
23
 * $column = (new ColumnSchemaBuilder(Schema::TYPE_INTEGER))->notNull()->defaultValue(0);
24
 * ```
25
 *
26
 * The AbstractColumnSchemaBuilder class provides a fluent interface, which means that the methods can be chained
27
 * together to create a column schema with multiple properties in a single line of code.
28
 */
29
abstract class AbstractColumnSchemaBuilder implements ColumnSchemaBuilderInterface
30
{
31
    /**
32
     * Internally used constants representing categories that abstract column types fall under.
33
     *
34
     * {@see $categoryMap} for mappings of abstract column types to category.
35
     */
36
    public const CATEGORY_PK = 'pk';
37
    public const CATEGORY_STRING = 'string';
38
    public const CATEGORY_NUMERIC = 'numeric';
39
    public const CATEGORY_TIME = 'time';
40
    public const CATEGORY_OTHER = 'other';
41
42
    protected bool|null $isNotNull = null;
43
    protected bool $isUnique = false;
44
    protected string|null $check = null;
45
    protected mixed $default = null;
46
    protected string|null $append = null;
47
    protected bool $isUnsigned = false;
48
    protected string|null $comment = null;
49
50
    /** @psalm-var string[] */
51
    private array $categoryMap = [
52
        SchemaInterface::TYPE_PK => self::CATEGORY_PK,
53
        SchemaInterface::TYPE_UPK => self::CATEGORY_PK,
54
        SchemaInterface::TYPE_BIGPK => self::CATEGORY_PK,
55
        SchemaInterface::TYPE_UBIGPK => self::CATEGORY_PK,
56
        SchemaInterface::TYPE_CHAR => self::CATEGORY_STRING,
57
        SchemaInterface::TYPE_STRING => self::CATEGORY_STRING,
58
        SchemaInterface::TYPE_TEXT => self::CATEGORY_STRING,
59
        SchemaInterface::TYPE_TINYINT => self::CATEGORY_NUMERIC,
60
        SchemaInterface::TYPE_SMALLINT => self::CATEGORY_NUMERIC,
61
        SchemaInterface::TYPE_INTEGER => self::CATEGORY_NUMERIC,
62
        SchemaInterface::TYPE_BIGINT => self::CATEGORY_NUMERIC,
63
        SchemaInterface::TYPE_FLOAT => self::CATEGORY_NUMERIC,
64
        SchemaInterface::TYPE_DOUBLE => self::CATEGORY_NUMERIC,
65
        SchemaInterface::TYPE_DECIMAL => self::CATEGORY_NUMERIC,
66
        SchemaInterface::TYPE_DATETIME => self::CATEGORY_TIME,
67
        SchemaInterface::TYPE_TIMESTAMP => self::CATEGORY_TIME,
68
        SchemaInterface::TYPE_TIME => self::CATEGORY_TIME,
69
        SchemaInterface::TYPE_DATE => self::CATEGORY_TIME,
70
        SchemaInterface::TYPE_BINARY => self::CATEGORY_OTHER,
71
        SchemaInterface::TYPE_BOOLEAN => self::CATEGORY_NUMERIC,
72
        SchemaInterface::TYPE_MONEY => self::CATEGORY_NUMERIC,
73
    ];
74
75
    /**
76
     * @psalm-param string[]|int[]|int|string|null $length
77
     */
78
    public function __construct(
79
        protected string $type,
80
        protected int|string|array|null $length = null
81
    ) {
82
    }
83
84
    public function notNull(): static
85
    {
86
        $this->isNotNull = true;
87
88
        return $this;
89
    }
90
91
    public function null(): static
92
    {
93
        $this->isNotNull = false;
94
95
        return $this;
96
    }
97
98
    public function unique(): static
99
    {
100
        $this->isUnique = true;
101
102
        return $this;
103
    }
104
105
    public function check(string|null $check): static
106
    {
107
        $this->check = $check;
108
109
        return $this;
110
    }
111
112
    public function defaultValue(mixed $default): static
113
    {
114
        if ($default === null) {
115
            $this->null();
116
        }
117
118
        $this->default = $default;
119
120
        return $this;
121
    }
122
123
    public function comment(string|null $comment): static
124
    {
125
        $this->comment = $comment;
126
127
        return $this;
128
    }
129
130
    /**
131
     * Marks column as unsigned.
132
     *
133
     * @return static The column schema builder instance itself.
134
     */
135
    public function unsigned(): static
136
    {
137
        $this->type = match ($this->type) {
138
            SchemaInterface::TYPE_PK => SchemaInterface::TYPE_UPK,
139
            SchemaInterface::TYPE_BIGPK => SchemaInterface::TYPE_UBIGPK,
140
            default => $this->type,
141
        };
142
143
        $this->isUnsigned = true;
144
145
        return $this;
146
    }
147
148
    public function defaultExpression(string $default): static
149
    {
150
        $this->default = new Expression($default);
151
152
        return $this;
153
    }
154
155
    public function append(string $sql): static
156
    {
157
        $this->append = $sql;
158
159
        return $this;
160
    }
161
162
    public function asString(): string
163
    {
164
        if ($this->getTypeCategory() === self::CATEGORY_PK) {
165
            $format = '{type}{check}{comment}{append}';
166
        } else {
167
            $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}';
168
        }
169
170
        return $this->buildCompleteString($format);
171
    }
172
173
    /**
174
     * Builds the length, precision part of the column.
175
     *
176
     * @return string A string containing the length/precision of the column.
177
     */
178
    protected function buildLengthString(): string
179
    {
180
        if (empty($this->length)) {
181
            return '';
182
        }
183
184
        if (is_array($this->length)) {
185
            $this->length = implode(',', $this->length);
186
        }
187
188
        return '(' . $this->length . ')';
0 ignored issues
show
Bug introduced by
Are you sure $this->length of type array|integer|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

188
        return '(' . /** @scrutinizer ignore-type */ $this->length . ')';
Loading history...
189
    }
190
191
    /**
192
     * Builds the not null constraint for the column.
193
     *
194
     * @return string A string 'NOT NULL' if {@see isNotNull} is true, 'NULL' if {@see isNotNull} is false or an empty
195
     * string otherwise.
196
     */
197
    protected function buildNotNullString(): string
198
    {
199
        if ($this->isNotNull === true) {
200
            return ' NOT NULL';
201
        }
202
203
        if ($this->isNotNull === false) {
204
            return ' NULL';
205
        }
206
207
        return '';
208
    }
209
210
    /**
211
     * Builds the unique constraint for the column.
212
     *
213
     * @return string A string 'UNIQUE' if {@see isUnique} is true, otherwise it returns an empty string.
214
     */
215
    protected function buildUniqueString(): string
216
    {
217
        return $this->isUnique ? ' UNIQUE' : '';
218
    }
219
220
    /**
221
     * Builds the default value specification for the column.
222
     *
223
     * @return string A string containing the DEFAULT keyword and the default value.
224
     */
225
    protected function buildDefaultString(): string
226
    {
227
        if ($this->default === null) {
228
            return $this->isNotNull === false ? ' DEFAULT NULL' : '';
229
        }
230
231
        $string = ' DEFAULT ';
232
233
        $string .= match (gettype($this->default)) {
234
            'object', 'integer' => (string)$this->default,
235
            'double' => NumericHelper::normalize((string)$this->default),
236
            'boolean' => $this->default ? 'TRUE' : 'FALSE',
237
            default => "'$this->default'",
238
        };
239
240
        return $string;
241
    }
242
243
    /**
244
     * Builds the check constraint for the column.
245
     *
246
     * @return string A string containing the CHECK constraint.
247
     */
248
    protected function buildCheckString(): string
249
    {
250
        return !empty($this->check) ? " CHECK ($this->check)" : '';
251
    }
252
253
    /**
254
     * Builds the unsigned string for column. Defaults to unsupported.
255
     *
256
     * @return string A string containing the UNSIGNED keyword.
257
     */
258
    protected function buildUnsignedString(): string
259
    {
260
        return '';
261
    }
262
263
    /**
264
     * Builds the custom string that's appended to column definition.
265
     *
266
     * @return string A string containing the custom SQL fragment appended to column definition.
267
     */
268
    protected function buildAppendString(): string
269
    {
270
        return !empty($this->append) ? ' ' . $this->append : '';
271
    }
272
273
    /**
274
     * @return string|null A string containing the column type category name.
275
     */
276
    protected function getTypeCategory(): string|null
277
    {
278
        return $this->categoryMap[$this->type] ?? null;
279
    }
280
281
    /**
282
     * Builds the comment specification for the column.
283
     *
284
     * @return string A string containing the COMMENT keyword and the comment itself.
285
     */
286
    protected function buildCommentString(): string
287
    {
288
        return '';
289
    }
290
291
    /**
292
     * Returns the complete column definition from input format.
293
     *
294
     * @param string $format The format of the definition.
295
     *
296
     * @return string A string containing the complete column definition.
297
     */
298
    protected function buildCompleteString(string $format): string
299
    {
300
        $placeholderValues = [
301
            '{type}' => $this->type,
302
            '{length}' => $this->buildLengthString(),
303
            '{unsigned}' => $this->buildUnsignedString(),
304
            '{notnull}' => $this->buildNotNullString(),
305
            '{unique}' => $this->buildUniqueString(),
306
            '{default}' => $this->buildDefaultString(),
307
            '{check}' => $this->buildCheckString(),
308
            '{comment}' => $this->buildCommentString(),
309
            '{append}' => $this->buildAppendString(),
310
        ];
311
312
        return strtr($format, $placeholderValues);
313
    }
314
315
    public function getType(): string|null
316
    {
317
        return $this->type;
318
    }
319
320
    public function getLength(): array|int|string|null
321
    {
322
        return $this->length;
323
    }
324
325
    public function isNotNull(): bool|null
326
    {
327
        return $this->isNotNull;
328
    }
329
330
    public function isUnique(): bool
331
    {
332
        return $this->isUnique;
333
    }
334
335
    public function getCheck(): string|null
336
    {
337
        return $this->check;
338
    }
339
340
    public function getDefault(): mixed
341
    {
342
        return $this->default;
343
    }
344
345
    public function getAppend(): string|null
346
    {
347
        return $this->append;
348
    }
349
350
    public function isUnsigned(): bool
351
    {
352
        return $this->isUnsigned;
353
    }
354
355
    public function getCategoryMap(): array
356
    {
357
        return $this->categoryMap;
358
    }
359
360
    public function getComment(): string|null
361
    {
362
        return $this->comment;
363
    }
364
}
365