Passed
Pull Request — master (#808)
by Sergei
02:31
created

ColumnDefinitionBuilder::build()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 16
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 22
rs 9.7333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\QueryBuilder;
6
7
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
8
use Yiisoft\Db\Expression\ExpressionInterface;
9
use Yiisoft\Db\Schema\Column\ColumnInterface;
10
use Yiisoft\Db\Schema\SchemaInterface;
11
12
use function gettype;
13
14
/**
15
 * Builds column definition from {@see ColumnInterface} object. Column definition is a string that represents
16
 * the column type and all constraints associated with the column. For example: `VARCHAR(128) NOT NULL DEFAULT 'foo'`.
17
 *
18
 * You can use {@see ColumnDefinitionBuilder} class in the following way:
19
 * `(string) (new ColumnDefinitionBuilder($column));`
20
 */
21
class ColumnDefinitionBuilder implements ColumnDefinitionBuilderInterface
22
{
23
    protected array $clauses = [
24
        'type',
25
        'unsigned',
26
        'null',
27
        'primary_key',
28
        'auto_increment',
29
        'unique',
30
        'default',
31
        'comment',
32
        'check',
33
        'references',
34
        'extra',
35
    ];
36
37
    public function __construct(
38
        protected QueryBuilderInterface $queryBuilder,
39
    ) {
40
    }
41
42
    public function build(ColumnInterface $column): string
43
    {
44
        $result = '';
45
46
        foreach ($this->clauses as $clause) {
47
            $result .= match ($clause) {
48
                'type' => $this->buildType($column),
49
                'unsigned' => $this->buildUnsigned($column),
50
                'null' => $this->buildNull($column),
51
                'primary_key' => $this->buildPrimaryKey($column),
52
                'auto_increment' => $this->buildAutoIncrement($column),
53
                'unique' => $this->buildUnique($column),
54
                'default' => $this->buildDefault($column),
55
                'comment' => $this->buildComment($column),
56
                'check' => $this->buildCheck($column),
57
                'references' => $this->buildReferences($column),
58
                'extra' => $this->buildExtra($column),
59
                default => '',
60
            };
61
        }
62
63
        return $result;
64
    }
65
66
    protected function buildType(ColumnInterface $column): string
67
    {
68
        if ($column->getDbType() === null) {
69
            $column = clone $column;
70
            $column->dbType($this->getDbType($column->getType()));
71
        }
72
73
        return (string) $column->getFullDbType();
74
    }
75
76
    /**
77
     * Builds the null or not null constraint for the column.
78
     *
79
     * @return string A string 'NOT NULL' if {@see ColumnInterface::allowNull} is false,
80
     * 'NULL' if {@see ColumnInterface::allowNull} is true or an empty string otherwise.
81
     */
82
    protected function buildNull(ColumnInterface $column): string
83
    {
84
        if ($column->isPrimaryKey()) {
85
            return '';
86
        }
87
88
        return match ($column->isAllowNull()) {
89
            true => ' NULL',
90
            false => ' NOT NULL',
91
            default => '',
92
        };
93
    }
94
95
    /**
96
     * Builds the primary key clause for column.
97
     *
98
     * @return string A string containing the PRIMARY KEY keyword.
99
     */
100
    public function buildPrimaryKey(ColumnInterface $column): string
101
    {
102
        return $column->isPrimaryKey() ? ' PRIMARY KEY' : '';
103
    }
104
105
    /**
106
     * Builds the auto increment clause for column. Default is empty string.
107
     *
108
     * @return string A string containing the AUTOINCREMENT keyword.
109
     */
110
    public function buildAutoIncrement(ColumnInterface $column): string
0 ignored issues
show
Unused Code introduced by
The parameter $column is not used and could be removed. ( Ignorable by Annotation )

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

110
    public function buildAutoIncrement(/** @scrutinizer ignore-unused */ ColumnInterface $column): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
111
    {
112
        return '';
113
    }
114
115
    /**
116
     * Builds the unique constraint for the column.
117
     *
118
     * @return string A string 'UNIQUE' if {@see isUnique} is true, otherwise it returns an empty string.
119
     */
120
    protected function buildUnique(ColumnInterface $column): string
121
    {
122
        if ($column->isPrimaryKey()) {
123
            return '';
124
        }
125
126
        return $column->isUnique() ? ' UNIQUE' : '';
127
    }
128
129
    /**
130
     * Builds the default value specification for the column.
131
     *
132
     * @return string A string containing the DEFAULT keyword and the default value.
133
     */
134
    protected function buildDefault(ColumnInterface $column): string
135
    {
136
        if ($column->isAutoIncrement()) {
137
            return '';
138
        }
139
140
        $defaultValue = $this->buildDefaultValue($column);
141
142
        if ($defaultValue === null) {
143
            return '';
144
        }
145
146
        return " DEFAULT $defaultValue";
147
    }
148
149
    /**
150
     * Return the default value for the column.
151
     *
152
     * @return string|null string with default value of column.
153
     */
154
    protected function buildDefaultValue(ColumnInterface $column): string|null
155
    {
156
        $value = $column->dbTypecast($column->getDefaultValue());
157
158
        if ($value === null) {
159
            return $column->isAllowNull() === true ? 'NULL' : null;
160
        }
161
162
        if ($value instanceof ExpressionInterface) {
163
            return $this->queryBuilder->buildExpression($value);
164
        }
165
166
        /** @var string */
167
        return match (gettype($value)) {
168
            'integer', 'double' => (string) $value,
169
            'boolean' => $value ? 'TRUE' : 'FALSE',
170
            default => $this->queryBuilder->quoter()->quoteValue((string) $value),
171
        };
172
    }
173
174
    /**
175
     * Builds the check constraint for the column.
176
     *
177
     * @return string A string containing the CHECK constraint.
178
     */
179
    protected function buildCheck(ColumnInterface $column): string
180
    {
181
        $check = $column->getCheck();
182
183
        return !empty($check) ? " CHECK ($check)" : '';
184
    }
185
186
    /**
187
     * Builds the unsigned string for column. Default is empty string.
188
     *
189
     * @return string A string containing the UNSIGNED keyword.
190
     */
191
    protected function buildUnsigned(ColumnInterface $column): string
192
    {
193
        return $column->isUnsigned() ? ' UNSIGNED' : '';
194
    }
195
196
    /**
197
     * Builds the custom string that's appended to column definition.
198
     *
199
     * @return string A string containing the custom SQL fragment appended to column definition.
200
     */
201
    protected function buildExtra(ColumnInterface $column): string
202
    {
203
        $extra = $column->getExtra();
204
205
        return !empty($extra) ? " $extra" : '';
206
    }
207
208
    /**
209
     * Builds the comment clause for the column. Default is empty string.
210
     *
211
     * @return string A string containing the COMMENT keyword and the comment itself.
212
     */
213
    protected function buildComment(ColumnInterface $column): string
0 ignored issues
show
Unused Code introduced by
The parameter $column is not used and could be removed. ( Ignorable by Annotation )

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

213
    protected function buildComment(/** @scrutinizer ignore-unused */ ColumnInterface $column): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
214
    {
215
        return '';
216
    }
217
218
    /**
219
     * Builds the references clause for the column.
220
     */
221
    private function buildReferences(ColumnInterface $column): string
222
    {
223
        $reference = $this->buildReferenceDefinition($column);
224
225
        if ($reference === null) {
226
            return '';
227
        }
228
229
        return "REFERENCES $reference";
230
    }
231
232
    /**
233
     * Builds the reference definition for the column.
234
     */
235
    protected function buildReferenceDefinition(ColumnInterface $column): string|null
236
    {
237
        /** @var ForeignKeyConstraint|null $reference */
238
        $reference = $column->getReference();
239
        $table = $reference?->getForeignTableName();
240
241
        if ($table === null) {
242
            return null;
243
        }
244
245
        $quoter = $this->queryBuilder->quoter();
246
247
        if (null !== $schema = $reference->getForeignSchemaName()) {
248
            $sql = $quoter->quoteTableName($schema) . '.' . $quoter->quoteTableName($table);
249
        } else {
250
            $sql = $quoter->quoteTableName($table);
251
        }
252
253
        $columns = $reference->getForeignColumnNames();
254
255
        if (!empty($columns)) {
256
            $sql .= ' (' . $this->queryBuilder->buildColumns($columns) . ')';
257
        }
258
259
        if (null !== $onDelete = $reference->getOnDelete()) {
260
            $sql .= ' ON DELETE ' . $onDelete;
261
        }
262
263
        if (null !== $onUpdate = $reference->getOnUpdate()) {
264
            $sql .= ' ON UPDATE ' . $onUpdate;
265
        }
266
267
        return $sql;
268
    }
269
270
    /**
271
     * Get the database column type from an abstract database type.
272
     *
273
     * @param string $type The abstract database type.
274
     *
275
     * @return string The database column type.
276
     */
277
    protected function getDbType(string $type): string
278
    {
279
        return match ($type) {
280
            SchemaInterface::TYPE_UUID => 'uuid',
281
            SchemaInterface::TYPE_CHAR => 'char',
282
            SchemaInterface::TYPE_STRING => 'varchar',
283
            SchemaInterface::TYPE_TEXT => 'text',
284
            SchemaInterface::TYPE_BINARY => 'binary',
285
            SchemaInterface::TYPE_BOOLEAN => 'boolean',
286
            SchemaInterface::TYPE_TINYINT => 'tinyint',
287
            SchemaInterface::TYPE_SMALLINT => 'smallint',
288
            SchemaInterface::TYPE_INTEGER => 'integer',
289
            SchemaInterface::TYPE_BIGINT => 'bigint',
290
            SchemaInterface::TYPE_FLOAT => 'float',
291
            SchemaInterface::TYPE_DOUBLE => 'double',
292
            SchemaInterface::TYPE_DECIMAL => 'decimal',
293
            SchemaInterface::TYPE_MONEY => 'money',
294
            SchemaInterface::TYPE_DATETIME => 'datetime',
295
            SchemaInterface::TYPE_TIMESTAMP => 'timestamp',
296
            SchemaInterface::TYPE_TIME => 'time',
297
            SchemaInterface::TYPE_DATE => 'date',
298
            SchemaInterface::TYPE_JSON => 'json',
299
            default => 'varchar',
300
        };
301
    }
302
}
303