Passed
Push — 2.x ( 416953...6a87a6 )
by Aleksei
29:18 queued 09:24
created

AbstractColumn::enum()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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

545
        $statement = [$driver->/** @scrutinizer ignore-call */ identifier($this->name), $this->type];
Loading history...
546
547
        if (static::isEnum($this)) {
548
            //Enum specific column options
549
            if (!empty($enumDefinition = $this->quoteEnum($driver))) {
550 64
                $statement[] = $enumDefinition;
551
            }
552 64
        } elseif (!empty($this->precision)) {
553
            $statement[] = "({$this->precision}, {$this->scale})";
554 64
        } elseif (!empty($this->size)) {
555
            $statement[] = "({$this->size})";
556 56
        }
557 56
558
        $statement[] = $this->nullable ? 'NULL' : 'NOT NULL';
559 56
560
        if ($this->defaultValue !== null) {
561
            $statement[] = "DEFAULT {$this->quoteDefault($driver)}";
562 1464
        }
563
564 1464
        return \implode(' ', $statement);
565
    }
566 1464
567
    public function compare(self $initial): bool
568 458
    {
569 458
        $normalized = clone $initial;
570
571 1440
        // soft compare, todo: improve
572 42
        if ($this == $normalized) {
573 1410
            return true;
574 878
        }
575
576
        $columnVars = \get_object_vars($this);
577 1464
        $dbColumnVars = \get_object_vars($normalized);
578
579 1464
        $difference = [];
580 634
        foreach ($columnVars as $name => $value) {
581
            if (\in_array($name, static::EXCLUDE_FROM_COMPARE, true)) {
582
                continue;
583 1464
            }
584
585
            if ($name === 'type') {
586 1894
                // user defined type
587
                if (!isset($this->mapping[$this->type]) && $this->type === $this->userType) {
588 1894
                    continue;
589
                }
590
            }
591 1894
592 1838
            if ($name === 'defaultValue') {
593
                $defaultValue = $this->getDefaultValue() instanceof FragmentInterface
594
                    ? $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

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

766
            return new Fragment(/** @scrutinizer ignore-type */ $value);
Loading history...
767
        }
768
769
        if ($value instanceof \DateTimeInterface) {
770
            $datetime = clone $value;
771
        } else {
772
            if (\is_numeric($value)) {
773
                //Presumably timestamp
774
                $datetime = new \DateTimeImmutable('now', $this->timezone);
775
                $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

775
                $datetime = $datetime->setTimestamp(/** @scrutinizer ignore-type */ $value);
Loading history...
776
            } else {
777
                $datetime = new \DateTimeImmutable($value, $this->timezone);
778
            }
779
        }
780
781
        return match ($type) {
782
            'datetime', 'timestamp' => $datetime,
783
            'time' => $datetime->format(static::TIME_FORMAT),
784
            'date' => $datetime->format(static::DATE_FORMAT),
785
            default => $value,
786
        };
787
    }
788
789
    /**
790
     * Get column comment.
791
     * An empty string will be returned if the feature is not supported by the driver.
792
     */
793
    public function getComment(): string
794
    {
795
        return '';
796
    }
797
}
798