Passed
Push — master ( 1feb1e...5c4177 )
by Wilmer
19:09 queued 16:48
created

AbstractColumnSchemaBuilder::notNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
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
    /**
163
     * Builds the full string for the column's schema including type, length, default value, not null and other SQL
164
     * fragment.
165
     *
166
     * @return string The SQL fragment that will be used for creating the column.
167
     */
168
    public function __toString(): string
169
    {
170
        if ($this->getTypeCategory() === self::CATEGORY_PK) {
171
            $format = '{type}{check}{comment}{append}';
172
        } else {
173
            $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}';
174
        }
175
176
        return $this->buildCompleteString($format);
177
    }
178
179
    /**
180
     * Builds the length, precision part of the column.
181
     *
182
     * @return string A string containing the length/precision of the column.
183
     */
184
    protected function buildLengthString(): string
185
    {
186
        if (empty($this->length)) {
187
            return '';
188
        }
189
190
        if (is_array($this->length)) {
191
            $this->length = implode(',', $this->length);
192
        }
193
194
        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

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