Passed
Pull Request — 2.x (#88)
by Aleksei
31:28 queued 11:27
created

MySQLColumn::isZerofill()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver\MySQL\Schema;
13
14
use Cycle\Database\Driver\DriverInterface;
15
use Cycle\Database\Exception\DefaultValueException;
16
use Cycle\Database\Injection\Fragment;
17
use Cycle\Database\Injection\FragmentInterface;
18
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
20
/**
21
 * Attention! You can use only one timestamp or datetime with DATETIME_NOW setting! Thought, it will
22
 * work on multiple fields with MySQL 5.6.6+ version.
23
 *
24
 * @method $this|AbstractColumn primary(int $size, bool $unsigned = false, $zerofill = false)
25
 * @method $this|AbstractColumn bigPrimary(int $size, bool $unsigned = false, $zerofill = false)
26
 * @method $this|AbstractColumn integer(int $size, bool $unsigned = false, $zerofill = false)
27
 * @method $this|AbstractColumn tinyInteger(int $size, bool $unsigned = false, $zerofill = false)
28
 * @method $this|AbstractColumn smallInteger(int $size, bool $unsigned = false, $zerofill = false)
29
 * @method $this|AbstractColumn bigInteger(int $size, bool $unsigned = false, $zerofill = false)
30
 */
31
class MySQLColumn extends AbstractColumn
32
{
33
    /**
34
     * Default timestamp expression (driver specific).
35
     */
36
    public const DATETIME_NOW = 'CURRENT_TIMESTAMP';
37
38
    protected const ENGINE_INTEGER_TYPES = ['tinyint', 'smallint', 'mediumint', 'int', 'bigint'];
39
40
    protected array $mapping = [
41
        //Primary sequences
42
        'primary'     => [
43
            'type'          => 'int',
44
            'size'          => 11,
45
            'autoIncrement' => true,
46
            'nullable'      => false,
47
        ],
48
        'bigPrimary'  => [
49
            'type'          => 'bigint',
50
            'size'          => 20,
51
            'autoIncrement' => true,
52
            'nullable'      => false,
53
        ],
54
55
        //Enum type (mapped via method)
56
        'enum'        => 'enum',
57
58
        //Logical types
59
        'boolean'     => ['type' => 'tinyint', 'size' => 1],
60
61
        //Integer types (size can always be changed with size method), longInteger has method alias
62
        //bigInteger
63
        'integer'     => ['type' => 'int', 'size' => 11, 'unsigned' => false, 'zerofill' => false],
64
        'tinyInteger' => ['type' => 'tinyint', 'size' => 4, 'unsigned' => false, 'zerofill' => false],
65
        'smallInteger'=> ['type' => 'smallint', 'size' => 6, 'unsigned' => false, 'zerofill' => false],
66
        'bigInteger'  => ['type' => 'bigint', 'size' => 20, 'unsigned' => false, 'zerofill' => false],
67
68
        //String with specified length (mapped via method)
69
        'string'      => ['type' => 'varchar', 'size' => 255],
70
71
        //Generic types
72
        'text'        => 'text',
73
        'tinyText'    => 'tinytext',
74
        'longText'    => 'longtext',
75
76
        //Real types
77
        'double'      => 'double',
78
        'float'       => 'float',
79
80
        //Decimal type (mapped via method)
81
        'decimal'     => 'decimal',
82
83
        //Date and Time types
84
        'datetime'    => 'datetime',
85
        'date'        => 'date',
86
        'time'        => 'time',
87
        'timestamp'   => ['type' => 'timestamp', 'defaultValue' => null],
88
89
        //Binary types
90
        'binary'      => 'blob',
91
        'tinyBinary'  => 'tinyblob',
92
        'longBinary'  => 'longblob',
93
94
        //Additional types
95
        'json'        => 'text',
96
        'uuid'        => ['type' => 'varchar', 'size' => 36],
97
    ];
98
99
    protected array $reverseMapping = [
100
        'primary'     => [['type' => 'int', 'autoIncrement' => true]],
101
        'bigPrimary'  => ['serial', ['type' => 'bigint', 'size' => 20, 'autoIncrement' => true]],
102
        'enum'        => ['enum'],
103
        'boolean'     => ['bool', 'boolean', ['type' => 'tinyint', 'size' => 1]],
104
        'integer'     => ['int', 'integer', 'mediumint'],
105
        'tinyInteger' => ['tinyint'],
106
        'smallInteger'=> ['smallint'],
107
        'bigInteger'  => ['bigint'],
108
        'string'      => ['varchar', 'char'],
109
        'text'        => ['text', 'mediumtext'],
110
        'tinyText'    => ['tinytext'],
111
        'longText'    => ['longtext'],
112
        'double'      => ['double'],
113
        'float'       => ['float', 'real'],
114
        'decimal'     => ['decimal'],
115
        'datetime'    => ['datetime'],
116
        'date'        => ['date'],
117
        'time'        => ['time'],
118
        'timestamp'   => ['timestamp'],
119
        'binary'      => ['blob', 'binary', 'varbinary'],
120
        'tinyBinary'  => ['tinyblob'],
121
        'longBinary'  => ['longblob'],
122
    ];
123
124
    /**
125
     * List of types forbids default value set.
126
     */
127
    protected array $forbiddenDefaults = [
128
        'text',
129
        'mediumtext',
130
        'tinytext',
131
        'longtext',
132
        'blob',
133
        'tinyblob',
134 474
        'longblob',
135
    ];
136 474
137
    /**
138 474
     * Column is auto incremental.
139
     */
140 214
    protected bool $autoIncrement = false;
141
142
    /**
143 474
     * Unsigned integer type. Related to {@see ENGINE_INTEGER_TYPES} only.
144
     */
145 474
    protected bool $unsigned = false;
146 474
147 332
    /**
148
     * Zerofill option. Related to {@see ENGINE_INTEGER_TYPES} only.
149
     */
150 456
    protected bool $zerofill = false;
151
152
    public function __call(string $type, array $arguments = []): AbstractColumn
153
    {
154
        if (\in_array($type, ['primary', 'bigPrimary', 'integer', 'tinyInteger', 'smallInteger', 'bigInteger'], true)) {
155
            return $this->type($type)->configureIntegerType(...$arguments);
156 470
        }
157
        return parent::__call($type, $arguments);
158 470
    }
159
160 470
    /**
161 470
     * @psalm-return non-empty-string
162 470
     */
163 470
    public function sqlStatement(DriverInterface $driver): string
164
    {
165
        if (\in_array($this->type, self::ENGINE_INTEGER_TYPES, true)) {
166 470
            return $this->sqlStatementInteger($driver);
167 470
        }
168 470
169
        $defaultValue = $this->defaultValue;
170
171
        if (\in_array($this->type, $this->forbiddenDefaults, true)) {
172
            //Flushing default value for forbidden types
173
            $this->defaultValue = null;
0 ignored issues
show
Bug Best Practice introduced by
The property defaultValue does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
174
        }
175
176 470
        $statement = parent::sqlStatement($driver);
177
178 470
        $this->defaultValue = $defaultValue;
179 470
        if ($this->autoIncrement) {
180 280
            return "{$statement} AUTO_INCREMENT";
181
        }
182 280
183 162
        return $statement;
184 162
    }
185
186 262
    /**
187
     * @psalm-param non-empty-string $table
188
     */
189
    public static function createInstance(string $table, array $schema, \DateTimeZone $timezone = null): self
190
    {
191 470
        $column = new self($table, $schema['Field'], $timezone);
192 446
193 446
        $column->type = $schema['Type'];
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
194 338
        $column->nullable = strtolower($schema['Null']) === 'yes';
0 ignored issues
show
Bug Best Practice introduced by
The property nullable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
195 338
        $column->defaultValue = $schema['Default'];
0 ignored issues
show
Bug Best Practice introduced by
The property defaultValue does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
196 320
        $column->autoIncrement = stripos($schema['Extra'], 'auto_increment') !== false;
197 6
198 6
        if (
199 316
            !preg_match(
200 2
                '/^(?P<type>[a-z]+)(?:\((?P<options>[^)]+)\))?(?: (?P<attr>[a-z ]+))?/',
201 2
                $column->type,
202
                $matches
203
            )
204
        ) {
205
            //No extra definitions
206
            return $column;
207 470
        }
208 152
209
        $column->type = $matches['type'];
210 152
211
        $options = [];
212
        if (!empty($matches['options'])) {
213
            $options = \explode(',', $matches['options']);
214 462
215
            if (count($options) > 1) {
216
                $column->precision = (int)$options[0];
0 ignored issues
show
Bug Best Practice introduced by
The property precision does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
217
                $column->scale = (int)$options[1];
0 ignored issues
show
Bug Best Practice introduced by
The property scale does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
218
            } else {
219
                $column->size = (int)$options[0];
0 ignored issues
show
Bug Best Practice introduced by
The property size does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
220 462
            }
221 462
        }
222
223
        if (!empty($matches['attr'])) {
224
            if (\in_array($column->type, self::ENGINE_INTEGER_TYPES, true)) {
225
                $intAttr = array_map('trim', explode(' ', $matches['attr']));
226
                if (\in_array('unsigned', $intAttr, true)) {
227 462
                    $column->unsigned = true;
228
                }
229
                if (\in_array('zerofill', $intAttr, true)) {
230
                    $column->zerofill = true;
231
                }
232
                unset($intAttr);
233
            }
234
        }
235
236
        // since 8.0 database does not provide size for some columns
237 170
        if ($column->size === 0) {
238
            switch ($column->type) {
239
                case 'int':
240
                    $column->size = 11;
241 170
                    break;
242
                case 'bigint':
243
                    $column->size = 20;
244
                    break;
245 170
                case 'tinyint':
246
                    $column->size = 4;
247
                    break;
248
                case 'smallint':
249
                    $column->size = 6;
250
                    break;
251
            }
252
        }
253
254
        //Fetching enum values
255
        if ($options !== [] && $column->getAbstractType() === 'enum') {
256
            $column->enumValues = \array_map(static fn ($value) => trim($value, $value[0]), $options);
0 ignored issues
show
Bug Best Practice introduced by
The property enumValues does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
257
258
            return $column;
259
        }
260
261
        //Default value conversions
262
        if ($column->type === 'bit' && $column->hasDefaultValue()) {
263
            //Cutting b\ and '
264
            $column->defaultValue = new Fragment($column->defaultValue);
265
        }
266
267
        if (
268
            $column->defaultValue === '0000-00-00 00:00:00'
269
            && $column->getAbstractType() === 'timestamp'
270
        ) {
271
            //Normalizing default value for timestamps
272
            $column->defaultValue = 0;
273
        }
274
275
        return $column;
276
    }
277
278
    public function compare(AbstractColumn $initial): bool
279
    {
280
        assert($initial instanceof self);
281
        return ! (!parent::compare($initial))
282
283
284
285
         ;
286
    }
287
288
    public function isUnsigned(): bool
289
    {
290
        return $this->unsigned;
291
    }
292
293
    public function isZerofill(): bool
294
    {
295
        return $this->zerofill;
296
    }
297
298
    public function unsigned(bool $value): static
299
    {
300
        $this->unsigned = $value;
301
302
        return $this;
303
    }
304
305
    public function zerofill(bool $value): static
306
    {
307
        $this->zerofill = $value;
308
309
        return $this;
310
    }
311
312
    /**
313
     * Ensure that datetime fields are correctly formatted.
314
     *
315
     * @psalm-param non-empty-string $type
316
     *
317
     * @throws DefaultValueException
318
     */
319
    protected function formatDatetime(
320
        string $type,
321
        string|int|\DateTimeInterface $value
322
    ): \DateTimeInterface|FragmentInterface|string {
323
        if ($value === 'current_timestamp()') {
324
            $value = self::DATETIME_NOW;
325
        }
326
327
        return parent::formatDatetime($type, $value);
328
    }
329
330
    private function configureIntegerType(
331
        ?int $size = null,
332
        ?bool $unsigned = null,
333
        ?bool $zerofill = null,
334
        ?bool $autoIncrement = null,
335
        ?bool $nullable = null,
336
    ): self {
337
        $this->size = $size ?? $this->size;
0 ignored issues
show
Bug Best Practice introduced by
The property size does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
338
        $this->unsigned = $unsigned ?? $this->unsigned;
339
        $this->zerofill = $zerofill ?? $this->zerofill;
340
        $this->autoIncrement = $autoIncrement ?? $this->autoIncrement;
341
        $this->nullable = $nullable ?? $this->nullable;
0 ignored issues
show
Bug Best Practice introduced by
The property nullable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
342
343
        return $this;
344
    }
345
346
    private function sqlStatementInteger(DriverInterface $driver): string
347
    {
348
        return \sprintf(
349
            '%s %s(%s)%s%s%s%s%s',
350
            $driver->identifier($this->name),
0 ignored issues
show
Bug introduced by
The method identifier() does not exist on Cycle\Database\Driver\DriverInterface. It seems like you code against a sub-type of Cycle\Database\Driver\DriverInterface such as Cycle\Database\Driver\Driver. ( Ignorable by Annotation )

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

350
            $driver->/** @scrutinizer ignore-call */ 
351
                     identifier($this->name),
Loading history...
351
            $this->type,
352
            $this->size,
353
            $this->unsigned ? ' UNSIGNED' : '',
354
            $this->zerofill ? ' ZEROFILL' : '',
355
            $this->nullable ? ' NULL' : ' NOT NULL',
356
            $this->defaultValue !== null ? " DEFAULT {$this->quoteDefault($driver)}" : '',
357
            $this->autoIncrement ? ' AUTO_INCREMENT' : ''
358
        );
359
    }
360
}
361