Skip to content

Commit be47889

Browse files
authored
Improve dbTypecast() method (#974)
1 parent ab68af3 commit be47889

13 files changed

Lines changed: 170 additions & 44 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- Enh #806, #964: Build `Expression` instances inside `Expression::$params` when build a query using `QueryBuilder` (@Tigrov)
1111
- Enh #766: Allow `ColumnInterface` as column type. (@Tigrov)
1212
- Bug #828: Fix `float` type when use `AbstractCommand::getRawSql()` method (@Tigrov)
13-
- New #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
13+
- New #752, #974: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
1414
for type casting performance (@Tigrov)
1515
- Enh #829: Rename `batchInsert()` to `insertBatch()` in `DMLQueryBuilderInterface` and `CommandInterface`
1616
and change parameters from `$table, $columns, $rows` to `$table, $rows, $columns = []` (@Tigrov)

rector.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
66
use Rector\Config\RectorConfig;
7+
use Rector\Php80\Rector\Ternary\GetDebugTypeRector;
78
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
89
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
910
use Rector\Set\ValueObject\LevelSetList;
@@ -23,6 +24,9 @@
2324
]);
2425

2526
$rectorConfig->skip([
27+
GetDebugTypeRector::class => [
28+
__DIR__ . '/tests/AbstractColumnTest.php',
29+
],
2630
ReadOnlyPropertyRector::class,
2731
NullToStrictStringFuncCallArgRector::class,
2832
]);

src/Schema/Column/AbstractColumn.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Yiisoft\Db\Schema\Column;
66

7+
use InvalidArgumentException;
78
use Yiisoft\Db\Constant\ColumnType;
89
use Yiisoft\Db\Constant\PhpType;
910
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
@@ -376,4 +377,9 @@ public function withName(string|null $name): static
376377
$new->name = $name;
377378
return $new;
378379
}
380+
381+
protected function throwWrongTypeException(string $type): never
382+
{
383+
throw new InvalidArgumentException("Wrong $type value for $this->type column.");
384+
}
379385
}

src/Schema/Column/BigIntColumn.php

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44

55
namespace Yiisoft\Db\Schema\Column;
66

7+
use BackedEnum;
8+
use DateTimeInterface;
9+
use Stringable;
710
use Yiisoft\Db\Constant\ColumnType;
811
use Yiisoft\Db\Expression\ExpressionInterface;
912
use Yiisoft\Db\Constant\GettypeResult;
1013
use Yiisoft\Db\Constant\PhpType;
1114

1215
use function gettype;
16+
use function is_int;
1317

1418
use const PHP_INT_MAX;
1519
use const PHP_INT_MIN;
@@ -23,21 +27,26 @@ class BigIntColumn extends AbstractColumn
2327

2428
public function dbTypecast(mixed $value): int|string|ExpressionInterface|null
2529
{
26-
/** @var ExpressionInterface|int|string|null */
30+
/**
31+
* @var ExpressionInterface|int|string|null
32+
* @psalm-suppress MixedArgument
33+
*/
2734
return match (gettype($value)) {
28-
GettypeResult::STRING => $value === '' ? null : (
29-
$value <= PHP_INT_MAX && $value >= PHP_INT_MIN
30-
? (int) $value
31-
: $value
32-
),
35+
GettypeResult::STRING => $this->dbTypecastString($value),
3336
GettypeResult::NULL => null,
3437
GettypeResult::INTEGER => $value,
38+
GettypeResult::DOUBLE => $this->dbTypecastString((string) $value),
3539
GettypeResult::BOOLEAN => $value ? 1 : 0,
36-
default => $value instanceof ExpressionInterface ? $value : (
37-
($val = (string) $value) <= PHP_INT_MAX && $val >= PHP_INT_MIN
38-
? (int) $val
39-
: $val
40-
),
40+
GettypeResult::OBJECT => match (true) {
41+
$value instanceof ExpressionInterface => $value,
42+
$value instanceof BackedEnum => is_int($value->value)
43+
? $value->value
44+
: $this->dbTypecastString($value->value),
45+
$value instanceof DateTimeInterface => $value->getTimestamp(),
46+
$value instanceof Stringable => $this->dbTypecastString((string) $value),
47+
default => $this->throwWrongTypeException($value::class),
48+
},
49+
default => $this->throwWrongTypeException(gettype($value)),
4150
};
4251
}
4352

@@ -55,4 +64,13 @@ public function phpTypecast(mixed $value): string|null
5564

5665
return (string) $value;
5766
}
67+
68+
protected function dbTypecastString(string $value): int|string|null
69+
{
70+
if ($value === '') {
71+
return null;
72+
}
73+
74+
return PHP_INT_MAX >= $value && $value >= PHP_INT_MIN ? (int) $value : $value;
75+
}
5876
}

src/Schema/Column/BinaryColumn.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
namespace Yiisoft\Db\Schema\Column;
66

7+
use BackedEnum;
78
use PDO;
9+
use Stringable;
810
use Yiisoft\Db\Command\Param;
911
use Yiisoft\Db\Constant\ColumnType;
1012
use Yiisoft\Db\Expression\ExpressionInterface;
@@ -25,8 +27,16 @@ public function dbTypecast(mixed $value): mixed
2527
GettypeResult::STRING => new Param($value, PDO::PARAM_LOB),
2628
GettypeResult::RESOURCE => $value,
2729
GettypeResult::NULL => null,
30+
GettypeResult::INTEGER => (string) $value,
31+
GettypeResult::DOUBLE => (string) $value,
2832
GettypeResult::BOOLEAN => $value ? '1' : '0',
29-
default => $value instanceof ExpressionInterface ? $value : (string) $value,
33+
GettypeResult::OBJECT => match (true) {
34+
$value instanceof ExpressionInterface => $value,
35+
$value instanceof BackedEnum => (string) $value->value,
36+
$value instanceof Stringable => (string) $value,
37+
default => $this->throwWrongTypeException($value::class),
38+
},
39+
default => $this->throwWrongTypeException(gettype($value)),
3040
};
3141
}
3242

src/Schema/Column/DateTimeColumn.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use DateTimeImmutable;
88
use DateTimeInterface;
99
use DateTimeZone;
10-
use InvalidArgumentException;
10+
use Stringable;
1111
use UnexpectedValueException;
1212
use Yiisoft\Db\Constant\ColumnType;
1313
use Yiisoft\Db\Constant\GettypeResult;
@@ -104,9 +104,10 @@ public function dbTypecast(mixed $value): string|ExpressionInterface|null
104104
$value instanceof DateTimeImmutable => $this->dbTypecastDateTime($value),
105105
$value instanceof DateTimeInterface => $this->dbTypecastDateTime(DateTimeImmutable::createFromInterface($value)),
106106
$value instanceof ExpressionInterface => $value,
107-
default => $this->dbTypecastString((string) $value),
107+
$value instanceof Stringable => $this->dbTypecastString((string) $value),
108+
default => $this->throwWrongTypeException($value::class),
108109
},
109-
default => throw new InvalidArgumentException('Wrong ' . gettype($value) . ' value for ' . $this->getType() . ' column.'),
110+
default => $this->throwWrongTypeException(gettype($value)),
110111
};
111112
}
112113

@@ -169,7 +170,7 @@ protected function getFormat(): string
169170
ColumnType::BIGINT => 'U',
170171
ColumnType::FLOAT => 'U.u',
171172
default => throw new UnexpectedValueException(
172-
'Unsupported abstract column type "' . $this->getType() . '" for ' . static::class . ' class.',
173+
'Unsupported abstract column type ' . $this->getType() . ' for ' . static::class . ' class.',
173174
),
174175
};
175176
}

src/Schema/Column/DoubleColumn.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44

55
namespace Yiisoft\Db\Schema\Column;
66

7+
use BackedEnum;
8+
use DateTimeInterface;
9+
use Stringable;
710
use Yiisoft\Db\Constant\ColumnType;
11+
use Yiisoft\Db\Constant\GettypeResult;
812
use Yiisoft\Db\Expression\ExpressionInterface;
913
use Yiisoft\Db\Constant\PhpType;
1014

11-
use function is_float;
15+
use function gettype;
16+
use function is_int;
1217

1318
/**
1419
* Represents the metadata for a double column.
@@ -17,15 +22,25 @@ class DoubleColumn extends AbstractColumn
1722
{
1823
protected const DEFAULT_TYPE = ColumnType::DOUBLE;
1924

20-
public function dbTypecast(mixed $value): float|ExpressionInterface|null
25+
public function dbTypecast(mixed $value): ExpressionInterface|float|int|null
2126
{
22-
if (is_float($value)) {
23-
return $value;
24-
}
25-
26-
return match ($value) {
27-
null, '' => null,
28-
default => $value instanceof ExpressionInterface ? $value : (float) $value,
27+
/** @var ExpressionInterface|float|int|null */
28+
return match (gettype($value)) {
29+
GettypeResult::DOUBLE => $value,
30+
GettypeResult::INTEGER => $value,
31+
GettypeResult::NULL => null,
32+
GettypeResult::STRING => $value === '' ? null : (float) $value,
33+
GettypeResult::BOOLEAN => $value ? 1.0 : 0.0,
34+
GettypeResult::OBJECT => match (true) {
35+
$value instanceof ExpressionInterface => $value,
36+
$value instanceof BackedEnum => $value->value === ''
37+
? null
38+
: (is_int($value->value) ? $value->value : (float) $value->value),
39+
$value instanceof DateTimeInterface => (float) $value->format('U.u'),
40+
$value instanceof Stringable => ($val = (string) $value) === '' ? null : (float) $val,
41+
default => $this->throwWrongTypeException($value::class),
42+
},
43+
default => $this->throwWrongTypeException(gettype($value)),
2944
};
3045
}
3146

src/Schema/Column/IntegerColumn.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
namespace Yiisoft\Db\Schema\Column;
66

77
use BackedEnum;
8+
use DateTimeInterface;
9+
use Stringable;
810
use Yiisoft\Db\Constant\ColumnType;
11+
use Yiisoft\Db\Constant\GettypeResult;
912
use Yiisoft\Db\Expression\ExpressionInterface;
1013
use Yiisoft\Db\Constant\PhpType;
1114

12-
use function is_int;
15+
use function gettype;
1316

1417
/**
1518
* Represents the schema for an integer column.
@@ -20,17 +23,21 @@ class IntegerColumn extends AbstractColumn
2023

2124
public function dbTypecast(mixed $value): int|ExpressionInterface|null
2225
{
23-
if (is_int($value)) {
24-
return $value;
25-
}
26-
27-
return match ($value) {
28-
null, '' => null,
29-
default => match (true) {
26+
/** @var ExpressionInterface|int|null */
27+
return match (gettype($value)) {
28+
GettypeResult::INTEGER => $value,
29+
GettypeResult::NULL => null,
30+
GettypeResult::STRING => $value === '' ? null : (int) $value,
31+
GettypeResult::DOUBLE => (int) $value,
32+
GettypeResult::BOOLEAN => $value ? 1 : 0,
33+
GettypeResult::OBJECT => match (true) {
3034
$value instanceof ExpressionInterface => $value,
31-
$value instanceof BackedEnum => (int) $value->value,
32-
default => (int) $value,
35+
$value instanceof BackedEnum => $value->value === '' ? null : (int) $value->value,
36+
$value instanceof DateTimeInterface => $value->getTimestamp(),
37+
$value instanceof Stringable => ($val = (string) $value) === '' ? null : (int) $val,
38+
default => $this->throwWrongTypeException($value::class),
3339
},
40+
default => $this->throwWrongTypeException(gettype($value)),
3441
};
3542
}
3643

src/Schema/Column/StringColumn.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Yiisoft\Db\Schema\Column;
66

77
use BackedEnum;
8+
use Stringable;
89
use Yiisoft\Db\Constant\ColumnType;
910
use Yiisoft\Db\Expression\ExpressionInterface;
1011
use Yiisoft\Db\Constant\GettypeResult;
@@ -25,13 +26,16 @@ public function dbTypecast(mixed $value): mixed
2526
GettypeResult::STRING => $value,
2627
GettypeResult::RESOURCE => $value,
2728
GettypeResult::NULL => null,
29+
GettypeResult::INTEGER => (string) $value,
30+
GettypeResult::DOUBLE => (string) $value,
2831
GettypeResult::BOOLEAN => $value ? '1' : '0',
2932
GettypeResult::OBJECT => match (true) {
3033
$value instanceof ExpressionInterface => $value,
3134
$value instanceof BackedEnum => (string) $value->value,
32-
default => (string) $value,
35+
$value instanceof Stringable => (string) $value,
36+
default => $this->throwWrongTypeException($value::class),
3337
},
34-
default => (string) $value,
38+
default => $this->throwWrongTypeException(gettype($value)),
3539
};
3640
}
3741

tests/AbstractColumnTest.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44

55
namespace Yiisoft\Db\Tests;
66

7+
use InvalidArgumentException;
8+
use PHPUnit\Framework\Attributes\DataProviderExternal;
79
use PHPUnit\Framework\TestCase;
810
use Yiisoft\Db\Schema\Column\ColumnInterface;
11+
use Yiisoft\Db\Tests\Provider\ColumnProvider;
912

13+
use function gettype;
1014
use function is_object;
1115

1216
abstract class AbstractColumnTest extends TestCase
1317
{
14-
/** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::predefinedTypes */
18+
#[DataProviderExternal(ColumnProvider::class, 'predefinedTypes')]
1519
public function testPredefinedType(string $className, string $type, string $phpType)
1620
{
1721
$column = new $className();
@@ -20,7 +24,7 @@ public function testPredefinedType(string $className, string $type, string $phpT
2024
$this->assertSame($phpType, $column->getPhpType());
2125
}
2226

23-
/** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::dbTypecastColumns */
27+
#[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')]
2428
public function testDbTypecastColumns(ColumnInterface $column, array $values)
2529
{
2630
// Set the timezone for testing purposes, could be any timezone except UTC
@@ -38,7 +42,18 @@ public function testDbTypecastColumns(ColumnInterface $column, array $values)
3842
date_default_timezone_set($oldDatetime);
3943
}
4044

41-
/** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::phpTypecastColumns */
45+
#[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumnsWithException')]
46+
public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value)
47+
{
48+
$type = is_object($value) ? $value::class : gettype($value);
49+
50+
$this->expectException(InvalidArgumentException::class);
51+
$this->expectExceptionMessage("Wrong $type value for {$column->getType()} column.");
52+
53+
$column->dbTypecast($value);
54+
}
55+
56+
#[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')]
4257
public function testPhpTypecastColumns(ColumnInterface $column, array $values)
4358
{
4459
foreach ($values as [$expected, $value]) {

0 commit comments

Comments
 (0)