AbstractColumn   F
last analyzed

Complexity

Total Complexity 97

Size/Duplication

Total Lines 740
Duplicated Lines 0 %

Test Coverage

Coverage 96.69%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 276
dl 0
loc 740
ccs 175
cts 181
cp 0.9669
rs 2
c 4
b 0
f 0
wmc 97

32 Methods

Rating   Name   Duplication   Size   Complexity  
A formatDatetime() 0 26 4
A __toString() 0 3 1
A isEnum() 0 3 1
B __call() 0 19 7
A quoteEnum() 0 8 3
A quoteDefault() 0 20 4
B __debugInfo() 0 37 7
A isJson() 0 3 1
A getDefaultValue() 0 21 6
A datetime() 0 11 3
B getAbstractType() 0 31 9
B sqlStatement() 0 22 7
A getPrecision() 0 3 1
A hasDefaultValue() 0 3 1
A getType() 0 10 3
A isNullable() 0 3 1
A getEnumValues() 0 3 1
A getScale() 0 3 1
A type() 0 34 5
A string() 0 9 2
A __construct() 0 6 1
A isReadonlySchema() 0 3 1
A defaultValue() 0 9 2
A enum() 0 9 2
A decimal() 0 10 2
A getDeclaredType() 0 3 1
A getInternalType() 0 3 1
C compare() 0 52 15
A getComment() 0 3 1
A nullable() 0 5 1
A getSize() 0 3 1
A getConstraints() 0 3 1

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.

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
/**
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\Schema;
13
14
use Cycle\Database\Driver\Jsoner;
15
use Cycle\Database\Schema\Attribute\ColumnAttribute;
16
use Cycle\Database\Schema\Traits\ColumnAttributesTrait;
17
use Cycle\Database\ColumnInterface;
18
use Cycle\Database\Driver\DriverInterface;
19
use Cycle\Database\Exception\DefaultValueException;
20
use Cycle\Database\Exception\SchemaException;
21
use Cycle\Database\Injection\Fragment;
22
use Cycle\Database\Injection\FragmentInterface;
23
use Cycle\Database\Query\QueryParameters;
24
use Cycle\Database\Schema\Traits\ElementTrait;
25
26
/**
27
 * Abstract column schema with read (see ColumnInterface) and write abilities. Must be implemented
28
 * by driver to support DBMS specific syntax and creation rules.
29
 *
30
 * Shortcuts for various column types:
31
 *
32
 * @method $this|AbstractColumn primary()
33
 * @method $this|AbstractColumn smallPrimary()
34
 * @method $this|AbstractColumn bigPrimary()
35
 * @method $this|AbstractColumn boolean()
36
 * @method $this|AbstractColumn integer()
37
 * @method $this|AbstractColumn tinyInteger()
38
 * @method $this|AbstractColumn smallInteger()
39
 * @method $this|AbstractColumn bigInteger()
40
 * @method $this|AbstractColumn text()
41
 * @method $this|AbstractColumn tinyText()
42
 * @method $this|AbstractColumn mediumText()
43
 * @method $this|AbstractColumn longText()
44
 * @method $this|AbstractColumn double()
45
 * @method $this|AbstractColumn float()
46
 * @method $this|AbstractColumn date()
47
 * @method $this|AbstractColumn time()
48
 * @method $this|AbstractColumn timestamp()
49
 * @method $this|AbstractColumn binary()
50
 * @method $this|AbstractColumn tinyBinary()
51
 * @method $this|AbstractColumn longBinary()
52
 * @method $this|AbstractColumn json()
53
 * @method $this|AbstractColumn snowflake()
54
 * @method $this|AbstractColumn ulid()
55
 * @method $this|AbstractColumn uuid()
56
 */
57
abstract class AbstractColumn implements ColumnInterface, ElementInterface
58
{
59
    use ColumnAttributesTrait;
60
    use ElementTrait;
61
62
    /**
63
     * Default timestamp expression (driver specific).
64
     */
65
    public const DATETIME_NOW = 'CURRENT_TIMESTAMP';
66
67
    /**
68
     * Value to be excluded from comparison.
69
     */
70
    public const EXCLUDE_FROM_COMPARE = ['timezone', 'userType', 'attributes'];
71
72
    /**
73
     * Normalization for time and dates.
74
     */
75
    public const DATE_FORMAT = 'Y-m-d';
76
77
    public const TIME_FORMAT = 'H:i:s';
78
    public const DATETIME_PRECISION = 6;
79
80
    /**
81
     * Mapping between abstract type and internal database type with it's options. Multiple abstract
82
     * types can map into one database type, this implementation allows us to equalize two columns
83
     * if they have different abstract types but same database one. Must be declared by DBMS
84
     * specific implementation.
85
     *
86
     * Example:
87
     * integer => array('type' => 'int', 'size' => 1),
88
     * boolean => array('type' => 'tinyint', 'size' => 1)
89
     *
90
     * @internal
91
     */
92
    protected array $mapping = [
93
        //Primary sequences
94
        'primary'     => null,
95
        'smallPrimary'  => null,
96
        'bigPrimary'  => null,
97
98
        //Enum type (mapped via method)
99
        'enum'        => null,
100
101
        //Logical types
102
        'boolean'     => null,
103
104
        //Integer types (size can always be changed with size method), longInteger has method alias
105
        //bigInteger
106
        'integer'     => null,
107
        'tinyInteger' => null,
108
        'smallInteger' => null,
109
        'bigInteger'  => null,
110
111
        //String with specified length (mapped via method)
112
        'string'      => null,
113
114
        //Generic types
115
        'text'        => null,
116
        'tinyText'    => null,
117
        'longText'    => null,
118
119
        //Real types
120
        'double'      => null,
121
        'float'       => null,
122
123
        //Decimal type (mapped via method)
124
        'decimal'     => null,
125
126
        //Date and Time types
127
        'datetime'    => null,
128
        'date'        => null,
129
        'time'        => null,
130
        'timestamp'   => null,
131
132
        //Binary types
133
        'binary'      => null,
134
        'tinyBinary'  => null,
135
        'longBinary'  => null,
136
137
        //Additional types
138
        'json'        => null,
139
    ];
140
141
    /**
142
     * Reverse mapping is responsible for generating abstract type based on database type and it's
143
     * options. Multiple database types can be mapped into one abstract type.
144
     *
145
     * @internal
146
     */
147
    protected array $reverseMapping = [
148
        'primary'     => [],
149
        'smallPrimary'  => [],
150
        'bigPrimary'  => [],
151
        'enum'        => [],
152
        'boolean'     => [],
153
        'integer'     => [],
154
        'tinyInteger' => [],
155
        'smallInteger' => [],
156
        'bigInteger'  => [],
157
        'string'      => [],
158
        'text'        => [],
159
        'tinyText'    => [],
160
        'longText'    => [],
161
        'double'      => [],
162
        'float'       => [],
163
        'decimal'     => [],
164
        'datetime'    => [],
165
        'date'        => [],
166
        'time'        => [],
167
        'timestamp'   => [],
168
        'binary'      => [],
169
        'tinyBinary'  => [],
170
        'longBinary'  => [],
171
        'json'        => [],
172
    ];
173
174
    /**
175
     * User defined type. Only until actual mapping.
176
     */
177
    protected ?string $userType = null;
178
179
    /**
180
     * DBMS specific column type.
181
     */
182
    protected string $type = '';
183
184
    protected ?\DateTimeZone $timezone = null;
185
186
    /**
187
     * Indicates that column can contain null values.
188
     */
189
    #[ColumnAttribute]
190
    protected bool $nullable = true;
191
192
    /**
193
     * Default column value, may not be applied to some datatypes (for example to primary keys),
194
     * should follow type size and other options.
195
     */
196
    #[ColumnAttribute]
197
    protected mixed $defaultValue = null;
198
199
    /**
200
     * Column type size, can have different meanings for different datatypes.
201
     */
202
    #[ColumnAttribute]
203
    protected int $size = 0;
204
205
    /**
206
     * Precision of column, applied only for "decimal" type.
207
     */
208
    #[ColumnAttribute(['decimal'])]
209
    protected int $precision = 0;
210
211
    /**
212
     * Scale of column, applied only for "decimal" type.
213
     */
214
    #[ColumnAttribute(['decimal'])]
215
    protected int $scale = 0;
216
217
    /**
218
     * List of allowed enum values.
219
     */
220
    protected array $enumValues = [];
221
222
    /**
223
     * Abstract type aliases (for consistency).
224
     */
225
    protected array $aliases = [
226
        'int'            => 'integer',
227
        'smallint'       => 'smallInteger',
228
        'bigint'         => 'bigInteger',
229
        'incremental'    => 'primary',
230
        'smallIncremental' => 'smallPrimary',
231
        'bigIncremental' => 'bigPrimary',
232 1950
        'bool'           => 'boolean',
233
        'blob'           => 'binary',
234
    ];
235
236
    /**
237 1950
     * Association list between abstract types and native PHP types. Every non listed type will be
238 1950
     * converted into string.
239
     *
240
     * @internal
241
     */
242
    private array $phpMapping = [
243
        self::INT   => ['primary', 'smallPrimary', 'bigPrimary', 'integer', 'tinyInteger', 'smallInteger', 'bigInteger'],
244
        self::BOOL  => ['boolean'],
245 1750
        self::FLOAT => ['double', 'float', 'decimal'],
246
    ];
247 1750
248
    /**
249
     * @psalm-param non-empty-string $table
250 856
     * @psalm-param non-empty-string $name
251
     */
252 856
    public function __construct(
253
        protected string $table,
254
        protected string $name,
255
        ?\DateTimeZone $timezone = null,
256
    ) {
257
        $this->timezone = $timezone ?? new \DateTimeZone(\date_default_timezone_get());
258 24
    }
259
260 24
    public function getSize(): int
261 24
    {
262
        return $this->size;
263 24
    }
264 24
265 24
    public function getPrecision(): int
266
    {
267
        return $this->precision;
268
    }
269 24
270 8
    public function getScale(): int
271
    {
272
        return $this->scale;
273 24
    }
274 24
275
    public function isNullable(): bool
276
    {
277 24
        return $this->nullable;
278 8
    }
279
280
    public function hasDefaultValue(): bool
281 24
    {
282
        return $this->defaultValue !== null;
283
    }
284
285 24
    /**
286 16
     * @throws DefaultValueException
287 16
     */
288
    public function getDefaultValue(): mixed
289
    {
290 24
        if (!$this->hasDefaultValue()) {
291
            return null;
292
        }
293 8
294
        if ($this->defaultValue instanceof FragmentInterface) {
295 8
            //Defined as SQL piece
296
            return $this->defaultValue;
297
        }
298 852
299
        if (\in_array($this->getAbstractType(), ['time', 'date', 'datetime', 'timestamp'])) {
300 852
            return $this->formatDatetime($this->getAbstractType(), $this->defaultValue);
301
        }
302
303 852
        return match ($this->getType()) {
304
            'int' => (int) $this->defaultValue,
305 852
            'float' => (float) $this->defaultValue,
306
            'bool' => \is_string($this->defaultValue) && \strtolower($this->defaultValue) === 'false'
307
                ? false : (bool) $this->defaultValue,
308 8
            default => (string) $this->defaultValue,
309
        };
310 8
    }
311
312
    /**
313 1798
     * Get every associated column constraint names.
314
     */
315 1798
    public function getConstraints(): array
316
    {
317
        return [];
318
    }
319
320
    /**
321 1798
     * Get allowed enum values.
322
     */
323 1798
    public function getEnumValues(): array
324 1514
    {
325
        return $this->enumValues;
326
    }
327 1074
328
    public function getInternalType(): string
329 368
    {
330
        return $this->type;
331
    }
332 850
333 682
    /**
334
     * @psalm-return non-empty-string
335
     */
336 698
    public function getType(): string
337 170
    {
338 293
        $schemaType = $this->getAbstractType();
339 205
        foreach ($this->phpMapping as $phpType => $candidates) {
340 205
            if (\in_array($schemaType, $candidates, true)) {
341 698
                return $phpType;
342
            }
343
        }
344
345
        return self::STRING;
346
    }
347
348 60
    /**
349
     * Returns type defined by the user, only until schema sync. Attention, this value is only preserved during the
350 60
     * declaration process. Value will become null after the schema fetched from database.
351
     *
352
     * @internal
353
     */
354
    public function getDeclaredType(): ?string
355
    {
356 852
        return $this->userType;
357
    }
358 852
359
    /**
360
     * DBMS specific reverse mapping must map database specific type into limited set of abstract
361 856
     * types.
362
     */
363 856
    public function getAbstractType(): string
364
    {
365
        foreach ($this->reverseMapping as $type => $candidates) {
366
            foreach ($candidates as $candidate) {
367
                if (\is_string($candidate)) {
368
                    if (\strtolower($candidate) === \strtolower($this->type)) {
369 842
                        return $type;
370
                    }
371 842
372 842
                    continue;
373 842
                }
374 618
375
                if (\strtolower($candidate['type']) !== \strtolower($this->type)) {
376
                    continue;
377
                }
378 754
379
                foreach ($candidate as $option => $required) {
380
                    if ($option === 'type') {
381
                        continue;
382
                    }
383
384
                    if ($this->{$option} !== $required) {
385
                        continue 2;
386
                    }
387
                }
388
389
                return $type;
390
            }
391
        }
392
393
        return 'unknown';
394
    }
395
396 1922
    /**
397
     * Give column new abstract type. DBMS specific implementation must map provided type into one
398 1922
     * of internal database values.
399 1922
     *
400 1922
     * Attention, changing type of existed columns in some databases has a lot of restrictions like
401 1890
     * cross type conversions and etc. Try do not change column type without a reason.
402 1862
     *
403
     * @psalm-param non-empty-string $abstract Abstract or virtual type declared in mapping.
404
     *
405 1838
     * @todo Support native database types (simply bypass abstractType)!
406
     */
407
    public function type(string $abstract): self
408 1414
    {
409 1360
        if (isset($this->aliases[$abstract])) {
410
            //Make recursive
411
            $abstract = $this->aliases[$abstract];
412 1034
        }
413 1034
414 1034
        if (!isset($this->mapping[$abstract])) {
415
            $this->type = $abstract;
416
            $this->userType = $abstract;
417 1034
418 876
            return $this;
419
        }
420
421
        // Originally specified type.
422 688
        $this->userType = $abstract;
423
424
        // Resetting all values to default state.
425
        $this->size = $this->precision = $this->scale = 0;
426 4
        $this->enumValues = [];
427
428
        // Abstract type points to DBMS specific type
429
        if (\is_string($this->mapping[$abstract])) {
430
            $this->type = $this->mapping[$abstract];
431
432
            return $this;
433
        }
434
435
        // Configuring column properties based on abstractType preferences
436
        foreach ($this->mapping[$abstract] as $property => $value) {
437
            $this->{$property} = $value;
438
        }
439
440
        return $this;
441
    }
442 1932
443
    /**
444 1932
     * Set column nullable/not nullable.
445
     */
446
    public function nullable(bool $nullable = true): self
447
    {
448
        $this->nullable = $nullable;
449 1932
450
        return $this;
451
    }
452 1932
453
    /**
454
     * Change column default value (can be forbidden for some column types).
455 1932
     * Use {@see AbstractColumn::DATETIME_NOW} to use driver specific NOW() function.
456 1932
     * Column with JSON type can be set to default value of array type.
457
     */
458
    public function defaultValue(mixed $value): self
459 1932
    {
460 1656
        $this->defaultValue = match (true) {
461
            $value === self::DATETIME_NOW => static::DATETIME_NOW,
462 1656
            static::isJson($this) !== false && \is_array($value) => Jsoner::toJson($value),
463
            default => $value,
464
        };
465
466 1442
        return $this;
467 1442
    }
468
469
    /**
470 1442
     * Set column as enum type and specify set of allowed values. Most of drivers will emulate enums
471
     * using column constraints.
472
     *
473
     * Examples:
474
     * $table->status->enum(['active', 'disabled']);
475
     * $table->status->enum('active', 'disabled');
476 698
     *
477
     * @param array|string $values Enum values (array or comma separated). String values only.
478 698
     */
479
    public function enum(string|array $values): self
480 698
    {
481
        $this->type('enum');
482
        $this->enumValues = \array_map(
483
            'strval',
484
            \is_array($values) ? $values : \func_get_args(),
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
485
        );
486
487 954
        return $this;
488
    }
489
490 954
    /**
491 578
     * Set column type as string with limited size. Maximum allowed size is 255 bytes, use "text"
492
     * abstract types for longer strings.
493
     *
494 954
     * Strings are perfect type to store email addresses as it big enough to store valid address
495
     * and
496 954
     * can be covered with unique index.
497
     *
498
     * @link http://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address
499
     *
500
     * @param int $size Max string length.
501
     *
502
     * @throws SchemaException
503
     */
504
    public function string(int $size = 255): self
505
    {
506
        $this->type('string');
507
508
        $size < 0 && throw new SchemaException('Invalid string length value');
509 304
510
        $this->size = $size;
511 304
512 304
        return $this;
513 304
    }
514 304
515
    public function datetime(int $size = 0, mixed ...$attributes): self
516
    {
517 304
        $this->type('datetime');
518
        $this->fillAttributes($attributes);
519
520
        ($size < 0 || $size > static::DATETIME_PRECISION) && throw new SchemaException(
521
            \sprintf('Invalid %s precision value.', $this->getAbstractType()),
522
        );
523
        $this->size = $size;
524
525
        return $this;
526
    }
527
528
    /**
529
     * Set column type as decimal with specific precision and scale.
530
     *
531
     * @throws SchemaException
532
     */
533
    public function decimal(int $precision, int $scale = 0): self
534 1008
    {
535
        $this->type('decimal');
536 1008
537
        empty($precision) && throw new SchemaException('Invalid precision value');
538 1008
539
        $this->precision = $precision;
540 1008
        $this->scale = $scale;
541
542 1008
        return $this;
543
    }
544
545
    public function sqlStatement(DriverInterface $driver): string
546
    {
547
        $statement = [$driver->identifier($this->name), $this->type];
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

547
        $statement = [$driver->/** @scrutinizer ignore-call */ identifier($this->name), $this->type];
Loading history...
548
549
        if (static::isEnum($this)) {
550 64
            //Enum specific column options
551
            if (!empty($enumDefinition = $this->quoteEnum($driver))) {
552 64
                $statement[] = $enumDefinition;
553
            }
554 64
        } elseif (!empty($this->precision)) {
555
            $statement[] = "({$this->precision}, {$this->scale})";
556 56
        } elseif (!empty($this->size)) {
557 56
            $statement[] = "({$this->size})";
558
        }
559 56
560
        $statement[] = $this->nullable ? 'NULL' : 'NOT NULL';
561
562 1464
        if ($this->defaultValue !== null) {
563
            $statement[] = "DEFAULT {$this->quoteDefault($driver)}";
564 1464
        }
565
566 1464
        return \implode(' ', $statement);
567
    }
568 458
569 458
    public function compare(self $initial): bool
570
    {
571 1440
        $normalized = clone $initial;
572 42
573 1410
        // soft compare, todo: improve
574 878
        if ($this == $normalized) {
575
            return true;
576
        }
577 1464
578
        $columnVars = \get_object_vars($this);
579 1464
        $dbColumnVars = \get_object_vars($normalized);
580 634
581
        $difference = [];
582
        foreach ($columnVars as $name => $value) {
583 1464
            if (\in_array($name, static::EXCLUDE_FROM_COMPARE, true)) {
584
                continue;
585
            }
586 1894
587
            if ($name === 'type') {
588 1894
                // user defined type
589
                if (!isset($this->mapping[$this->type]) && $this->type === $this->userType) {
590
                    continue;
591 1894
                }
592 1838
            }
593
594
            if ($name === 'defaultValue') {
595 1598
                $defaultValue = $this->getDefaultValue() instanceof FragmentInterface
596 1598
                    ? $this->getDefaultValue()->__toString()
0 ignored issues
show
Bug introduced by
The method __toString() does not exist on Cycle\Database\Injection\FragmentInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Cycle\Database\Query\QueryInterface or Spiral\Database\Injection\FragmentInterface or Spiral\Database\Query\QueryInterface or Cycle\Database\Query\ReturningInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

596
                    ? $this->getDefaultValue()->/** @scrutinizer ignore-call */ __toString()
Loading history...
597
                    : $this->getDefaultValue();
598 1598
                $initialDefaultValue = $initial->getDefaultValue() instanceof FragmentInterface
599 1598
                    ? $initial->getDefaultValue()->__toString()
600 1598
                    : $initial->getDefaultValue();
601 1598
602
                //Default values has to compared using type-casted value
603
                if ($defaultValue != $initialDefaultValue) {
604 1598
                    $difference[] = $name;
605
                } elseif (
606 1598
                    $defaultValue !== $initialDefaultValue
607 380
                    && (!\is_object($this->getDefaultValue()) && !\is_object($initial->getDefaultValue()))
608
                ) {
609 1554
                    $difference[] = $name;
610 1554
                }
611
612 16
                continue;
613
            }
614
615 1598
            if ($value !== $dbColumnVars[$name]) {
616
                $difference[] = $name;
617
            }
618 1598
        }
619 154
620
        return empty($difference);
621
    }
622
623 1598
    public function isReadonlySchema(): bool
624
    {
625
        return $this->getAttributes()['readonlySchema'] ?? false;
626
    }
627
628
    /**
629 152
     * Get column comment.
630
     * An empty string will be returned if the feature is not supported by the driver.
631 152
     */
632 152
    public function getComment(): string
633 152
    {
634
        return '';
635
    }
636 152
637
    /**
638
     * Shortcut for AbstractColumn->type() method.
639
     *
640
     * @psalm-param non-empty-string $name
641
     */
642 846
    public function __call(string $name, array $arguments = []): self
643
    {
644 846
        if (isset($this->aliases[$name]) || isset($this->mapping[$name])) {
645 846
            $this->type($name);
646
        }
647
648
        // The type must be set before the attributes are filled.
649 846
        !empty($this->type) or throw new SchemaException('Undefined abstract/virtual type');
650 578
651 578
        if (\count($arguments) === 1 && \key($arguments) === 0) {
652 578
            if (\array_key_exists($name, $this->getAttributesMap())) {
653
                $this->fillAttributes([$name => $arguments[0]]);
654
                return $this;
655
            }
656
        }
657 798
658 410
        $this->fillAttributes($arguments);
659 293
660 83
        return $this;
661 798
    }
662
663
    public function __toString(): string
664
    {
665
        return $this->table . '.' . $this->getName();
666
    }
667
668
    /**
669
     * Simplified way to dump information.
670
     */
671
    public function __debugInfo(): array
672 682
    {
673
        $column = [
674
            'name' => $this->name,
675
            'type' => [
676 682
                'database' => $this->type,
677
                'schema'   => $this->getAbstractType(),
678 578
                'php'      => $this->getType(),
679
            ],
680
        ];
681 634
682 8
        if (!empty($this->size)) {
683
            $column['size'] = $this->size;
684 634
        }
685
686 32
        if ($this->nullable) {
687 32
            $column['nullable'] = true;
688
        }
689 632
690
        if ($this->defaultValue !== null) {
691
            $column['defaultValue'] = $this->getDefaultValue();
692
        }
693 634
694 32
        if (static::isEnum($this)) {
695 301
            $column['enumValues'] = $this->enumValues;
696
        }
697 634
698
        if ($this->getAbstractType() === 'decimal') {
699
            $column['precision'] = $this->precision;
700
            $column['scale'] = $this->scale;
701
        }
702
703
        if ($this->attributes !== []) {
704
            $column['attributes'] = $this->attributes;
705
        }
706
707
        return $column;
708
    }
709
710
    protected static function isEnum(self $column): bool
711
    {
712
        return $column->getAbstractType() === 'enum';
713
    }
714
715
    /**
716
     * Checks if the column is JSON or no.
717
     *
718
     * Returns null if it's impossible to explicitly define the JSON type.
719
     */
720
    protected static function isJson(self $column): ?bool
721
    {
722
        return $column->getAbstractType() === 'json';
723
    }
724
725
    /**
726
     * Get database specific enum type definition options.
727
     */
728
    protected function quoteEnum(DriverInterface $driver): string
729
    {
730
        $enumValues = [];
731
        foreach ($this->enumValues as $value) {
732
            $enumValues[] = $driver->quote($value);
733
        }
734
735
        return !empty($enumValues) ? '(' . \implode(', ', $enumValues) . ')' : '';
736
    }
737
738
    /**
739
     * Must return driver specific default value.
740
     */
741
    protected function quoteDefault(DriverInterface $driver): string
742
    {
743
        $defaultValue = $this->getDefaultValue();
744
        if ($defaultValue === null) {
745
            return 'NULL';
746
        }
747
748
        if ($defaultValue instanceof FragmentInterface) {
749
            return $driver->getQueryCompiler()->compile(
750
                new QueryParameters(),
751
                '',
752
                $defaultValue,
753
            );
754
        }
755
756
        return match ($this->getType()) {
757
            'bool' => $defaultValue ? 'TRUE' : 'FALSE',
758
            'float' => \sprintf('%F', $defaultValue),
759
            'int' => (string) $defaultValue,
760
            default => $driver->quote($defaultValue),
761
        };
762
    }
763
764
    /**
765
     * Ensure that datetime fields are correctly formatted.
766
     *
767
     * @psalm-param non-empty-string $type
768
     *
769
     * @throws DefaultValueException
770
     */
771
    protected function formatDatetime(
772
        string $type,
773
        string|int|\DateTimeInterface $value,
774
    ): \DateTimeInterface|FragmentInterface|string {
775
        if ($value === static::DATETIME_NOW) {
776
            //Dynamic default value
777
            return new Fragment($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type DateTimeInterface; however, parameter $fragment of Cycle\Database\Injection\Fragment::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

777
            return new Fragment(/** @scrutinizer ignore-type */ $value);
Loading history...
778
        }
779
780
        if ($value instanceof \DateTimeInterface) {
781
            $datetime = clone $value;
782
        } else {
783
            if (\is_numeric($value)) {
784
                //Presumably timestamp
785
                $datetime = new \DateTimeImmutable('now', $this->timezone);
786
                $datetime = $datetime->setTimestamp($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $timestamp of DateTimeImmutable::setTimestamp() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

786
                $datetime = $datetime->setTimestamp(/** @scrutinizer ignore-type */ $value);
Loading history...
787
            } else {
788
                $datetime = new \DateTimeImmutable($value, $this->timezone);
789
            }
790
        }
791
792
        return match ($type) {
793
            'datetime', 'timestamp' => $datetime,
794
            'time' => $datetime->format(static::TIME_FORMAT),
795
            'date' => $datetime->format(static::DATE_FORMAT),
796
            default => $value,
797
        };
798
    }
799
}
800