Completed
Branch feature/pre-split (04a10a)
by Anton
03:10
created

AbstractColumn   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 499
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 499
rs 6.3005
c 1
b 0
f 0
wmc 58
lcom 1
cbo 5

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getType() 0 4 1
A phpType() 0 11 3
A getSize() 0 4 1
A getPrecision() 0 4 1
A getScale() 0 4 1
A isNullable() 0 4 1
A hasDefaultValue() 0 4 1
D getDefaultValue() 0 32 9
A getConstraints() 0 4 1
A getEnumValues() 0 4 1
D abstractType() 0 32 9
B compare() 0 30 6
C sqlStatement() 0 23 7
B __debugInfo() 0 34 6
A prepareEnum() 0 13 3
C prepareDefault() 0 24 7

How to fix   Complexity   

Complex Class

Complex classes like AbstractColumn often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractColumn, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\Database\Schemas\Prototypes;
8
9
use Spiral\Database\Entities\Driver;
10
use Spiral\Database\Injections\Fragment;
11
use Spiral\Database\Injections\FragmentInterface;
12
use Spiral\Database\Schemas\ColumnInterface;
13
14
/**
15
 * Abstract column schema with read (see ColumnInterface) and write abilities. Must be implemented
16
 * by driver to support DBMS specific syntax and creation rules.
17
 *
18
 * Shortcuts for various column types:
19
 *
20
 * @method AbstractColumn|$this boolean()
21
 * @method AbstractColumn|$this integer()
22
 * @method AbstractColumn|$this tinyInteger()
23
 * @method AbstractColumn|$this bigInteger()
24
 * @method AbstractColumn|$this text()
25
 * @method AbstractColumn|$this tinyText()
26
 * @method AbstractColumn|$this longText()
27
 * @method AbstractColumn|$this double()
28
 * @method AbstractColumn|$this float()
29
 * @method AbstractColumn|$this datetime()
30
 * @method AbstractColumn|$this date()
31
 * @method AbstractColumn|$this time()
32
 * @method AbstractColumn|$this timestamp()
33
 * @method AbstractColumn|$this binary()
34
 * @method AbstractColumn|$this tinyBinary()
35
 * @method AbstractColumn|$this longBinary()
36
 * @method AbstractColumn|$this json()
37
 */
38
abstract class AbstractColumn extends AbstractElement implements ColumnInterface
39
{
40
    /**
41
     * Default datetime value.
42
     */
43
    const DATETIME_DEFAULT = '1970-01-01 00:00:00';
44
45
    /**
46
     * Default timestamp expression (driver specific).
47
     */
48
    const DATETIME_CURRENT = 'CURRENT_TIMESTAMP';
49
50
    /**
51
     * Abstract type aliases (for consistency).
52
     *
53
     * @var array
54
     */
55
    private $aliases = [
56
        'int'            => 'integer',
57
        'bigint'         => 'bigInteger',
58
        'incremental'    => 'primary',
59
        'bigIncremental' => 'bigPrimary',
60
        'bool'           => 'boolean',
61
        'blob'           => 'binary',
62
    ];
63
64
    /**
65
     * Association list between abstract types and native PHP types. Every non listed type will be
66
     * converted into string.
67
     *
68
     * @invisible
69
     *
70
     * @var array
71
     */
72
    private $phpMapping = [
73
        self::INT   => ['primary', 'bigPrimary', 'integer', 'tinyInteger', 'bigInteger'],
74
        self::BOOL  => ['boolean'],
75
        self::FLOAT => ['double', 'float', 'decimal'],
76
    ];
77
78
    /**
79
     * Mapping between abstract type and internal database type with it's options. Multiple abstract
80
     * types can map into one database type, this implementation allows us to equalize two columns
81
     * if they have different abstract types but same database one. Must be declared by DBMS
82
     * specific implementation.
83
     *
84
     * Example:
85
     * integer => array('type' => 'int', 'size' => 1),
86
     * boolean => array('type' => 'tinyint', 'size' => 1)
87
     *
88
     * @invisible
89
     *
90
     * @var array
91
     */
92
    protected $mapping = [
93
        //Primary sequences
94
        'primary'     => null,
95
        'bigPrimary'  => null,
96
97
        //Enum type (mapped via method)
98
        'enum'        => null,
99
100
        //Logical types
101
        'boolean'     => null,
102
103
        //Integer types (size can always be changed with size method), longInteger has method alias
104
        //bigInteger
105
        'integer'     => null,
106
        'tinyInteger' => null,
107
        'bigInteger'  => null,
108
109
        //String with specified length (mapped via method)
110
        'string'      => null,
111
112
        //Generic types
113
        'text'        => null,
114
        'tinyText'    => null,
115
        'longText'    => null,
116
117
        //Real types
118
        'double'      => null,
119
        'float'       => null,
120
121
        //Decimal type (mapped via method)
122
        'decimal'     => null,
123
124
        //Date and Time types
125
        'datetime'    => null,
126
        'date'        => null,
127
        'time'        => null,
128
        'timestamp'   => null,
129
130
        //Binary types
131
        'binary'      => null,
132
        'tinyBinary'  => null,
133
        'longBinary'  => null,
134
135
        //Additional types
136
        'json'        => null,
137
    ];
138
139
    /**
140
     * Reverse mapping is responsible for generating abstact type based on database type and it's
141
     * options. Multiple database types can be mapped into one abstract type.
142
     *
143
     * @invisible
144
     *
145
     * @var array
146
     */
147
    protected $reverseMapping = [
148
        'primary'     => [],
149
        'bigPrimary'  => [],
150
        'enum'        => [],
151
        'boolean'     => [],
152
        'integer'     => [],
153
        'tinyInteger' => [],
154
        'bigInteger'  => [],
155
        'string'      => [],
156
        'text'        => [],
157
        'tinyText'    => [],
158
        'longText'    => [],
159
        'double'      => [],
160
        'float'       => [],
161
        'decimal'     => [],
162
        'datetime'    => [],
163
        'date'        => [],
164
        'time'        => [],
165
        'timestamp'   => [],
166
        'binary'      => [],
167
        'tinyBinary'  => [],
168
        'longBinary'  => [],
169
        'json'        => [],
170
    ];
171
172
    /**
173
     * DBMS specific column type.
174
     *
175
     * @var string
176
     */
177
    protected $type = '';
178
179
    /**
180
     * Indicates that column can contain null values.
181
     *
182
     * @var bool
183
     */
184
    protected $nullable = true;
185
186
    /**
187
     * Default column value, may not be applied to some datatypes (for example to primary keys),
188
     * should follow type size and other options.
189
     *
190
     * @var mixed
191
     */
192
    protected $defaultValue = null;
193
194
    /**
195
     * Column type size, can have different meanings for different datatypes.
196
     *
197
     * @var int
198
     */
199
    protected $size = 0;
200
201
    /**
202
     * Precision of column, applied only for "decimal" type.
203
     *
204
     * @var int
205
     */
206
    protected $precision = 0;
207
208
    /**
209
     * Scale of column, applied only for "decimal" type.
210
     *
211
     * @var int
212
     */
213
    protected $scale = 0;
214
215
    /**
216
     * List of allowed enum values.
217
     *
218
     * @var array
219
     */
220
    protected $enumValues = [];
221
222
    /**
223
     * {@inheritdoc}
224
     */
225
    public function getType(): string
226
    {
227
        return $this->type;
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233
    public function phpType(): string
234
    {
235
        $schemaType = $this->abstractType();
236
        foreach ($this->phpMapping as $phpType => $candidates) {
237
            if (in_array($schemaType, $candidates)) {
238
                return $phpType;
239
            }
240
        }
241
242
        return self::STRING;
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248
    public function getSize(): int
249
    {
250
        return $this->size;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256
    public function getPrecision(): int
257
    {
258
        return $this->precision;
259
    }
260
261
    /**
262
     * {@inheritdoc}
263
     */
264
    public function getScale(): int
265
    {
266
        return $this->scale;
267
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272
    public function isNullable(): bool
273
    {
274
        return $this->nullable;
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     */
280
    public function hasDefaultValue(): bool
281
    {
282
        return !is_null($this->defaultValue);
283
    }
284
285
    /**
286
     * {@inheritdoc}
287
     */
288
    public function getDefaultValue()
289
    {
290
        if (!$this->hasDefaultValue()) {
291
            return null;
292
        }
293
294
        if ($this->defaultValue instanceof FragmentInterface) {
295
            return $this->defaultValue;
296
        }
297
298
        if (in_array($this->abstractType(), ['time', 'date', 'datetime', 'timestamp'])) {
299
            //Driver specific now expression
300
            if (strtolower($this->defaultValue) == strtolower(static::DATETIME_CURRENT)) {
301
                return new Fragment($this->defaultValue);
302
            }
303
        }
304
305
        switch ($this->phpType()) {
306
            case 'int':
307
                return (int)$this->defaultValue;
308
            case 'float':
309
                return (float)$this->defaultValue;
310
            case 'bool':
311
                if (strtolower($this->defaultValue) == 'false') {
312
                    return false;
313
                }
314
315
                return (bool)$this->defaultValue;
316
        }
317
318
        return (string)$this->defaultValue;
319
    }
320
321
    /**
322
     * Get every associated column constraint names.
323
     *
324
     * @return array
325
     */
326
    public function getConstraints(): array
327
    {
328
        return [];
329
    }
330
331
    /**
332
     * Get allowed enum values.
333
     *
334
     * @return array
335
     */
336
    public function getEnumValues(): array
337
    {
338
        return $this->enumValues;
339
    }
340
341
    /**
342
     * DBMS specific reverse mapping must map database specific type into limited set of abstract
343
     * types.
344
     *
345
     * @return string
346
     */
347
    public function abstractType(): string
348
    {
349
        foreach ($this->reverseMapping as $type => $candidates) {
350
            foreach ($candidates as $candidate) {
351
                if (is_string($candidate)) {
352
                    if (strtolower($candidate) == strtolower($this->type)) {
353
                        return $type;
354
                    }
355
356
                    continue;
357
                }
358
359
                if (strtolower($candidate['type']) != strtolower($this->type)) {
360
                    continue;
361
                }
362
363
                foreach ($candidate as $option => $required) {
364
                    if ($option == 'type') {
365
                        continue;
366
                    }
367
368
                    if ($this->{$option} != $required) {
369
                        continue 2;
370
                    }
371
                }
372
373
                return $type;
374
            }
375
        }
376
377
        return 'unknown';
378
    }
379
380
    //--- MODIFICATIONS IS HERE
381
382
    /**
383
     * {@inheritdoc}
384
     */
385
    public function compare(ColumnInterface $initial): bool
386
    {
387
        $normalized = clone $initial;
388
389
        if ($this == $normalized) {
390
            return true;
391
        }
392
393
        $columnVars = get_object_vars($this);
394
        $dbColumnVars = get_object_vars($normalized);
395
396
        $difference = [];
397
        foreach ($columnVars as $name => $value) {
398
            if ($name == 'defaultValue') {
399
400
                //Default values has to compared using type-casted value
401
                if ($this->getDefaultValue() != $initial->getDefaultValue()) {
402
                    $difference[] = $name;
403
                }
404
405
                continue;
406
            }
407
408
            if ($value != $dbColumnVars[$name]) {
409
                $difference[] = $name;
410
            }
411
        }
412
413
        return empty($difference);
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     */
419
    public function sqlStatement(Driver $driver): string
420
    {
421
        $statement = [$driver->identifier($this->name), $this->type];
422
423
        if ($this->abstractType() == 'enum') {
424
            //Enum specific column options
425
            if (!empty($enumDefinition = $this->prepareEnum($driver))) {
426
                $statement[] = $enumDefinition;
427
            }
428
        } elseif (!empty($this->precision)) {
429
            $statement[] = "({$this->precision}, {$this->scale})";
430
        } elseif (!empty($this->size)) {
431
            $statement[] = "({$this->size})";
432
        }
433
434
        $statement[] = $this->nullable ? 'NULL' : 'NOT NULL';
435
436
        if ($this->defaultValue !== null) {
437
            $statement[] = "DEFAULT {$this->prepareDefault($driver)}";
438
        }
439
440
        return implode(' ', $statement);
441
    }
442
443
444
    /**
445
     * Simplified way to dump information.
446
     *
447
     * @return array
448
     */
449
    public function __debugInfo()
450
    {
451
        $column = [
452
            'name' => $this->name,
453
            'type' => [
454
                'database' => $this->type,
455
                'schema'   => $this->abstractType(),
456
                'php'      => $this->phpType(),
457
            ],
458
        ];
459
460
        if (!empty($this->size)) {
461
            $column['size'] = $this->size;
462
        }
463
464
        if ($this->nullable) {
465
            $column['nullable'] = true;
466
        }
467
468
        if ($this->defaultValue !== null) {
469
            $column['defaultValue'] = $this->getDefaultValue();
470
        }
471
472
        if ($this->abstractType() == 'enum') {
473
            $column['enumValues'] = $this->enumValues;
474
        }
475
476
        if ($this->abstractType() == 'decimal') {
477
            $column['precision'] = $this->precision;
478
            $column['scale'] = $this->scale;
479
        }
480
481
        return $column;
482
    }
483
484
    /**
485
     * Get database specific enum type definition options.
486
     *
487
     * @param Driver $driver
488
     *
489
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
490
     */
491
    protected function prepareEnum(Driver $driver): string
492
    {
493
        $enumValues = [];
494
        foreach ($this->enumValues as $value) {
495
            $enumValues[] = $driver->quote($value);
496
        }
497
498
        if (!empty($enumValues)) {
499
            return '(' . implode(', ', $enumValues) . ')';
500
        }
501
502
        return '';
503
    }
504
505
    /**
506
     * Must return driver specific default value.
507
     *
508
     * @param Driver $driver
509
     *
510
     * @return string
511
     */
512
    protected function prepareDefault(Driver $driver): string
513
    {
514
        if (($defaultValue = $this->getDefaultValue()) === null) {
515
            return 'NULL';
516
        }
517
518
        if ($defaultValue instanceof FragmentInterface) {
519
            return $defaultValue->sqlStatement();
520
        }
521
522
        if ($this->phpType() == 'bool') {
523
            return $defaultValue ? 'TRUE' : 'FALSE';
524
        }
525
526
        if ($this->phpType() == 'float') {
527
            return sprintf('%F', $defaultValue);
528
        }
529
530
        if ($this->phpType() == 'int') {
531
            return $defaultValue;
532
        }
533
534
        return $driver->quote($defaultValue);
535
    }
536
}