Completed
Branch feature/pre-split (76ded7)
by Anton
03:22
created

AbstractColumn::getEnumValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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