Passed
Pull Request — 2.x (#138)
by Maxim
18:04
created

AbstractColumn::isJson()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

615
        $statement = [$driver->/** @scrutinizer ignore-call */ identifier($this->name), $this->type];
Loading history...
616
617
        if (static::isEnum($this)) {
618 1598
            //Enum specific column options
619 154
            if (!empty($enumDefinition = $this->quoteEnum($driver))) {
620
                $statement[] = $enumDefinition;
621
            }
622
        } elseif (!empty($this->precision)) {
623 1598
            $statement[] = "({$this->precision}, {$this->scale})";
624
        } elseif (!empty($this->size)) {
625
            $statement[] = "({$this->size})";
626
        }
627
628
        $statement[] = $this->nullable ? 'NULL' : 'NOT NULL';
629 152
630
        if ($this->defaultValue !== null) {
631 152
            $statement[] = "DEFAULT {$this->quoteDefault($driver)}";
632 152
        }
633 152
634
        return \implode(' ', $statement);
635
    }
636 152
637
    public function compare(self $initial): bool
638
    {
639
        $normalized = clone $initial;
640
641
        // soft compare, todo: improve
642 846
        if ($this == $normalized) {
643
            return true;
644 846
        }
645 846
646
        $columnVars = get_object_vars($this);
647
        $dbColumnVars = get_object_vars($normalized);
648
649 846
        $difference = [];
650 578
        foreach ($columnVars as $name => $value) {
651 578
            if (\in_array($name, static::EXCLUDE_FROM_COMPARE, true)) {
652 578
                continue;
653
            }
654
655
            if ($name === 'type') {
656
                // user defined type
657 798
                if (!isset($this->mapping[$this->type]) && $this->type === $this->userType) {
658 410
                    continue;
659 293
                }
660 83
            }
661 798
662
            if ($name === 'defaultValue') {
663
                //Default values has to compared using type-casted value
664
                if ($this->getDefaultValue() != $initial->getDefaultValue()) {
665
                    $difference[] = $name;
666
                } elseif (
667
                    $this->getDefaultValue() !== $initial->getDefaultValue()
668
                    && (!\is_object($this->getDefaultValue()) && !\is_object($initial->getDefaultValue()))
669
                ) {
670
                    $difference[] = $name;
671
                }
672 682
673
                continue;
674
            }
675
676 682
            if ($value !== $dbColumnVars[$name]) {
677
                $difference[] = $name;
678 578
            }
679
        }
680
681 634
        return empty($difference);
682 8
    }
683
684 634
    public function isReadonlySchema(): bool
685
    {
686 32
        return $this->getAttributes()['readonlySchema'] ?? false;
687 32
    }
688
689 632
    /**
690
     * Get database specific enum type definition options.
691
     */
692
    protected function quoteEnum(DriverInterface $driver): string
693 634
    {
694 32
        $enumValues = [];
695 301
        foreach ($this->enumValues as $value) {
696
            $enumValues[] = $driver->quote($value);
697 634
        }
698
699
        return !empty($enumValues) ? '(' . implode(', ', $enumValues) . ')' : '';
700
    }
701
702
    /**
703
     * Must return driver specific default value.
704
     */
705
    protected function quoteDefault(DriverInterface $driver): string
706
    {
707
        $defaultValue = $this->getDefaultValue();
708
        if ($defaultValue === null) {
709
            return 'NULL';
710
        }
711
712
        if ($defaultValue instanceof FragmentInterface) {
713
            return $driver->getQueryCompiler()->compile(
714
                new QueryParameters(),
715
                '',
716
                $defaultValue
717
            );
718
        }
719
720
        return match ($this->getType()) {
721
            'bool' => $defaultValue ? 'TRUE' : 'FALSE',
722
            'float' => sprintf('%F', $defaultValue),
723
            'int' => (string) $defaultValue,
724
            default => $driver->quote($defaultValue)
725
        };
726
    }
727
728
    /**
729
     * Ensure that datetime fields are correctly formatted.
730
     *
731
     * @psalm-param non-empty-string $type
732
     *
733
     * @throws DefaultValueException
734
     */
735
    protected function formatDatetime(
736
        string $type,
737
        string|int|\DateTimeInterface $value
738
    ): \DateTimeInterface|FragmentInterface|string {
739
        if ($value === static::DATETIME_NOW) {
740
            //Dynamic default value
741
            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

741
            return new Fragment(/** @scrutinizer ignore-type */ $value);
Loading history...
742
        }
743
744
        if ($value instanceof \DateTimeInterface) {
745
            $datetime = clone $value;
746
        } else {
747
            if (is_numeric($value)) {
748
                //Presumably timestamp
749
                $datetime = new DateTimeImmutable('now', $this->timezone);
750
                $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

750
                $datetime = $datetime->setTimestamp(/** @scrutinizer ignore-type */ $value);
Loading history...
751
            } else {
752
                $datetime = new DateTimeImmutable($value, $this->timezone);
753
            }
754
        }
755
756
        return match ($type) {
757
            'datetime', 'timestamp' => $datetime,
758
            'time' => $datetime->format(static::TIME_FORMAT),
759
            'date' => $datetime->format(static::DATE_FORMAT),
760
            default => $value
761
        };
762
    }
763
764
    protected static function isEnum(self $column): bool
765
    {
766
        return $column->getAbstractType() === 'enum';
767
    }
768
769
    /**
770
     * Checks if the column is JSON or no.
771
     *
772
     * Returns null if it's impossible to explicitly define the JSON type.
773
     */
774
    protected static function isJson(self $column): ?bool
775
    {
776
        return $column->getAbstractType() === 'json';
777
    }
778
}
779