Passed
Pull Request — master (#594)
by Def
08:59 queued 06:41
created

AbstractColumn::isNotNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Schema\Builder;
6
7
use Yiisoft\Db\Expression\Expression;
8
use Yiisoft\Db\Helper\StringHelper;
9
use Yiisoft\Db\Schema\SchemaInterface;
10
11
use function gettype;
12
use function implode;
13
use function strtr;
14
15
/**
16
 * Is a utility class that provides a convenient way to create column schemas for {@see AbstractSchema}.
17
 *
18
 * It provides methods for specifying the properties of a column, such as its type, size, default value, and whether it
19
 * is nullable or not. It also provides a method for creating a column schema based on the specified properties.
20
 *
21
 * For example, the following code creates a column schema for an integer column:
22
 *
23
 * ```php
24
 * $column = (new Column(SchemaInterface::TYPE_INTEGER))->notNull()->defaultValue(0);
25
 * ```
26
 *
27
 * Provides a fluent interface, which means that the methods can be chained together to create a column schema with
28
 * many properties in a single line of code.
29
 */
30
abstract class AbstractColumn implements ColumnInterface
31
{
32
    /**
33
     * Internally used constants representing categories that abstract column types fall under.
34
     *
35
     * {@see $categoryMap} For mappings of abstract column types to category.
36
     */
37
    public const CATEGORY_PK = 'pk';
38
    public const CATEGORY_STRING = 'string';
39
    public const CATEGORY_NUMERIC = 'numeric';
40
    public const CATEGORY_TIME = 'time';
41
    public const CATEGORY_OTHER = 'other';
42
43
    protected bool|null $isNotNull = null;
44
    protected bool $isUnique = false;
45
    protected string|null $check = null;
46
    protected mixed $default = null;
47
    protected string|null $append = null;
48
    protected bool $isUnsigned = false;
49
    protected string|null $comment = null;
50
    protected string $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}';
51
52
    /** @psalm-var string[] */
53
    private array $categoryMap = [
54
        SchemaInterface::TYPE_PK => self::CATEGORY_PK,
55
        SchemaInterface::TYPE_UPK => self::CATEGORY_PK,
56
        SchemaInterface::TYPE_BIGPK => self::CATEGORY_PK,
57
        SchemaInterface::TYPE_UBIGPK => self::CATEGORY_PK,
58
        SchemaInterface::TYPE_CHAR => self::CATEGORY_STRING,
59
        SchemaInterface::TYPE_STRING => self::CATEGORY_STRING,
60
        SchemaInterface::TYPE_TEXT => self::CATEGORY_STRING,
61
        SchemaInterface::TYPE_TINYINT => self::CATEGORY_NUMERIC,
62
        SchemaInterface::TYPE_SMALLINT => self::CATEGORY_NUMERIC,
63
        SchemaInterface::TYPE_INTEGER => self::CATEGORY_NUMERIC,
64
        SchemaInterface::TYPE_BIGINT => self::CATEGORY_NUMERIC,
65
        SchemaInterface::TYPE_FLOAT => self::CATEGORY_NUMERIC,
66
        SchemaInterface::TYPE_DOUBLE => self::CATEGORY_NUMERIC,
67
        SchemaInterface::TYPE_DECIMAL => self::CATEGORY_NUMERIC,
68
        SchemaInterface::TYPE_DATETIME => self::CATEGORY_TIME,
69
        SchemaInterface::TYPE_TIMESTAMP => self::CATEGORY_TIME,
70
        SchemaInterface::TYPE_TIME => self::CATEGORY_TIME,
71
        SchemaInterface::TYPE_DATE => self::CATEGORY_TIME,
72
        SchemaInterface::TYPE_BINARY => self::CATEGORY_OTHER,
73
        SchemaInterface::TYPE_BOOLEAN => self::CATEGORY_NUMERIC,
74
        SchemaInterface::TYPE_MONEY => self::CATEGORY_NUMERIC,
75
    ];
76
77
    /**
78
     * @psalm-param string[]|int[]|int|string|null $length
79
     */
80
    public function __construct(
81
        protected string $type,
82
        protected int|string|array|null $length = null
83
    ) {
84
    }
85
86
    public function notNull(): static
87
    {
88
        $this->isNotNull = true;
89
        return $this;
90
    }
91
92
    public function null(): static
93
    {
94
        $this->isNotNull = false;
95
        return $this;
96
    }
97
98
    public function unique(): static
99
    {
100
        $this->isUnique = true;
101
        return $this;
102
    }
103
104
    public function check(string|null $check): static
105
    {
106
        $this->check = $check;
107
        return $this;
108
    }
109
110
    public function defaultValue(mixed $default): static
111
    {
112
        if ($default === null) {
113
            $this->null();
114
        }
115
116
        $this->default = $default;
117
118
        return $this;
119
    }
120
121
    public function comment(string|null $comment): static
122
    {
123
        $this->comment = $comment;
124
        return $this;
125
    }
126
127
    /**
128
     * Marks column as unsigned.
129
     */
130
    public function unsigned(): static
131
    {
132
        $this->type = match ($this->type) {
133
            SchemaInterface::TYPE_PK => SchemaInterface::TYPE_UPK,
134
            SchemaInterface::TYPE_BIGPK => SchemaInterface::TYPE_UBIGPK,
135
            default => $this->type,
136
        };
137
138
        $this->isUnsigned = true;
139
140
        return $this;
141
    }
142
143
    public function defaultExpression(string $default): static
144
    {
145
        $this->default = new Expression($default);
146
        return $this;
147
    }
148
149
    public function setFormat(string $format): void
150
    {
151
        $this->format = $format;
152
    }
153
154
    public function append(string $sql): static
155
    {
156
        $this->append = $sql;
157
        return $this;
158
    }
159
160
    public function asString(): string
161
    {
162
        if ($this->getTypeCategory() === self::CATEGORY_PK) {
163
            $format = '{type}{check}{comment}{append}';
164
        } else {
165
            $format = $this->format;
166
        }
167
168
        return $this->buildCompleteString($format);
169
    }
170
171
    public function getType(): string|null
172
    {
173
        return $this->type;
174
    }
175
176
    public function getLength(): array|int|string|null
177
    {
178
        return $this->length;
179
    }
180
181
    public function isNotNull(): bool|null
182
    {
183
        return $this->isNotNull;
184
    }
185
186
    public function isUnique(): bool
187
    {
188
        return $this->isUnique;
189
    }
190
191
    public function getCheck(): string|null
192
    {
193
        return $this->check;
194
    }
195
196
    public function getDefault(): mixed
197
    {
198
        return $this->default;
199
    }
200
201
    public function getAppend(): string|null
202
    {
203
        return $this->append;
204
    }
205
206
    public function isUnsigned(): bool
207
    {
208
        return $this->isUnsigned;
209
    }
210
211
    public function getCategoryMap(): array
212
    {
213
        return $this->categoryMap;
214
    }
215
216
    public function getComment(): string|null
217
    {
218
        return $this->comment;
219
    }
220
221
    /**
222
     * Builds the length, precision part of the column.
223
     *
224
     * @return string A string containing the length/precision of the column.
225
     */
226
    protected function buildLengthString(): string
227
    {
228
        if (empty($this->length)) {
229
            return '';
230
        }
231
232
        if (is_array($this->length)) {
233
            $this->length = implode(',', $this->length);
234
        }
235
236
        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

236
        return '(' . /** @scrutinizer ignore-type */ $this->length . ')';
Loading history...
237
    }
238
239
    /**
240
     * Builds the not null constraint for the column.
241
     *
242
     * @return string A string 'NOT NULL' if {@see isNotNull} is true, 'NULL' if {@see isNotNull} is false or an empty
243
     * string otherwise.
244
     */
245
    protected function buildNotNullString(): string
246
    {
247
        if ($this->isNotNull === true) {
248
            return ' NOT NULL';
249
        }
250
251
        if ($this->isNotNull === false) {
252
            return ' NULL';
253
        }
254
255
        return '';
256
    }
257
258
    /**
259
     * Builds the unique constraint for the column.
260
     *
261
     * @return string A string 'UNIQUE' if {@see isUnique} is true, otherwise it returns an empty string.
262
     */
263
    protected function buildUniqueString(): string
264
    {
265
        return $this->isUnique ? ' UNIQUE' : '';
266
    }
267
268
    /**
269
     * Return the default value for the column.
270
     *
271
     * @return string|null string with default value of column.
272
     */
273
    protected function buildDefaultValue(): string|null
274
    {
275
        if ($this->default === null) {
276
            return $this->isNotNull === false ? 'NULL' : null;
277
        }
278
279
        return match (gettype($this->default)) {
280
            'object', 'integer' => (string) $this->default,
281
            'double' => StringHelper::normalizeFloat((string) $this->default),
282
            'boolean' => $this->default ? 'TRUE' : 'FALSE',
283
            default => "'$this->default'",
284
        };
285
    }
286
287
    /**
288
     * Builds the default value specification for the column.
289
     *
290
     * @return string A string containing the DEFAULT keyword and the default value.
291
     */
292
    protected function buildDefaultString(): string
293
    {
294
        $defaultValue = $this->buildDefaultValue();
295
        if ($defaultValue === null) {
296
            return '';
297
        }
298
299
        return ' DEFAULT ' . $defaultValue;
300
    }
301
302
    /**
303
     * Builds the check constraint for the column.
304
     *
305
     * @return string A string containing the CHECK constraint.
306
     */
307
    protected function buildCheckString(): string
308
    {
309
        return !empty($this->check) ? " CHECK ($this->check)" : '';
310
    }
311
312
    /**
313
     * Builds the unsigned string for column. Defaults to unsupported.
314
     *
315
     * @return string A string containing the UNSIGNED keyword.
316
     */
317
    protected function buildUnsignedString(): string
318
    {
319
        return '';
320
    }
321
322
    /**
323
     * Builds the custom string that's appended to column definition.
324
     *
325
     * @return string A string containing the custom SQL fragment appended to column definition.
326
     */
327
    protected function buildAppendString(): string
328
    {
329
        return !empty($this->append) ? ' ' . $this->append : '';
330
    }
331
332
    /**
333
     * @return string|null A string containing the column type category name.
334
     */
335
    protected function getTypeCategory(): string|null
336
    {
337
        return $this->categoryMap[$this->type] ?? null;
338
    }
339
340
    /**
341
     * Builds the comment specification for the column.
342
     *
343
     * @return string A string containing the COMMENT keyword and the comment itself.
344
     */
345
    protected function buildCommentString(): string
346
    {
347
        return '';
348
    }
349
350
    /**
351
     * Returns the complete column definition from input format.
352
     *
353
     * @param string $format The format of the definition.
354
     *
355
     * @return string A string containing the complete column definition.
356
     */
357
    protected function buildCompleteString(string $format): string
358
    {
359
        $placeholderValues = [
360
            '{type}' => $this->type,
361
            '{length}' => $this->buildLengthString(),
362
            '{unsigned}' => $this->buildUnsignedString(),
363
            '{notnull}' => $this->buildNotNullString(),
364
            '{unique}' => $this->buildUniqueString(),
365
            '{default}' => $this->buildDefaultString(),
366
            '{check}' => $this->buildCheckString(),
367
            '{comment}' => $this->buildCommentString(),
368
            '{append}' => $this->buildAppendString(),
369
        ];
370
371
        return strtr($format, $placeholderValues);
372
    }
373
}
374