From 4d53ece03f2ee5b1df4dcaac55ed95de040c3598 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 18 May 2025 17:02:49 +0700 Subject: [PATCH 01/14] Improve `dbTypecast()` method --- src/Schema/Column/AbstractColumn.php | 6 +++++ src/Schema/Column/BigIntColumn.php | 32 +++++++++++++++++-------- src/Schema/Column/BinaryColumn.php | 12 +++++++++- src/Schema/Column/DateTimeColumn.php | 9 +++---- src/Schema/Column/DoubleColumn.php | 27 ++++++++++++++------- src/Schema/Column/IntegerColumn.php | 24 ++++++++++++------- src/Schema/Column/StringColumn.php | 8 +++++-- tests/AbstractColumnTest.php | 26 ++++++++++++++++----- tests/Provider/ColumnProvider.php | 35 ++++++++++++++++++++++++++++ 9 files changed, 139 insertions(+), 40 deletions(-) diff --git a/src/Schema/Column/AbstractColumn.php b/src/Schema/Column/AbstractColumn.php index 5876136ab..1c61f0d8e 100644 --- a/src/Schema/Column/AbstractColumn.php +++ b/src/Schema/Column/AbstractColumn.php @@ -4,6 +4,7 @@ namespace Yiisoft\Db\Schema\Column; +use InvalidArgumentException; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PhpType; use Yiisoft\Db\Constraint\ForeignKeyConstraint; @@ -376,4 +377,9 @@ public function withName(string|null $name): static $new->name = $name; return $new; } + + protected function throwWrongTypeException(string $type): never + { + throw new InvalidArgumentException("Wrong $type value for $this->type column."); + } } diff --git a/src/Schema/Column/BigIntColumn.php b/src/Schema/Column/BigIntColumn.php index 772d7a897..7927d1187 100644 --- a/src/Schema/Column/BigIntColumn.php +++ b/src/Schema/Column/BigIntColumn.php @@ -4,6 +4,9 @@ namespace Yiisoft\Db\Schema\Column; +use BackedEnum; +use DateTimeInterface; +use Stringable; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Constant\GettypeResult; @@ -25,19 +28,19 @@ public function dbTypecast(mixed $value): int|string|ExpressionInterface|null { /** @var ExpressionInterface|int|string|null */ return match (gettype($value)) { - GettypeResult::STRING => $value === '' ? null : ( - $value <= PHP_INT_MAX && $value >= PHP_INT_MIN - ? (int) $value - : $value - ), + GettypeResult::STRING => $this->dbTypecastString($value), GettypeResult::NULL => null, GettypeResult::INTEGER => $value, + GettypeResult::DOUBLE => $this->dbTypecastString((string) $value), GettypeResult::BOOLEAN => $value ? 1 : 0, - default => $value instanceof ExpressionInterface ? $value : ( - ($val = (string) $value) <= PHP_INT_MAX && $val >= PHP_INT_MIN - ? (int) $val - : $val - ), + GettypeResult::OBJECT => match (true) { + $value instanceof ExpressionInterface => $value, + $value instanceof BackedEnum => $this->dbTypecastString((string) $value->value), + $value instanceof DateTimeInterface => $value->getTimestamp(), + $value instanceof Stringable => $this->dbTypecastString((string) $value), + default => $this->throwWrongTypeException($value::class), + }, + default => $this->throwWrongTypeException(gettype($value)), }; } @@ -55,4 +58,13 @@ public function phpTypecast(mixed $value): string|null return (string) $value; } + + protected function dbTypecastString(string $value): int|string|null + { + if ($value === '') { + return null; + } + + return PHP_INT_MAX >= $value && $value >= PHP_INT_MIN ? (int) $value : $value; + } } diff --git a/src/Schema/Column/BinaryColumn.php b/src/Schema/Column/BinaryColumn.php index 6bc1e4e8c..a370af3f5 100644 --- a/src/Schema/Column/BinaryColumn.php +++ b/src/Schema/Column/BinaryColumn.php @@ -4,7 +4,9 @@ namespace Yiisoft\Db\Schema\Column; +use BackedEnum; use PDO; +use Stringable; use Yiisoft\Db\Command\Param; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\ExpressionInterface; @@ -25,8 +27,16 @@ public function dbTypecast(mixed $value): mixed GettypeResult::STRING => new Param($value, PDO::PARAM_LOB), GettypeResult::RESOURCE => $value, GettypeResult::NULL => null, + GettypeResult::INTEGER, + GettypeResult::DOUBLE => (string) $value, GettypeResult::BOOLEAN => $value ? '1' : '0', - default => $value instanceof ExpressionInterface ? $value : (string) $value, + GettypeResult::OBJECT => match (true) { + $value instanceof ExpressionInterface => $value, + $value instanceof BackedEnum => (string) $value->value, + $value instanceof Stringable => (string) $value, + default => $this->throwWrongTypeException($value::class), + }, + default => $this->throwWrongTypeException(gettype($value)), }; } diff --git a/src/Schema/Column/DateTimeColumn.php b/src/Schema/Column/DateTimeColumn.php index 6e4cd64a8..e6573056d 100644 --- a/src/Schema/Column/DateTimeColumn.php +++ b/src/Schema/Column/DateTimeColumn.php @@ -7,7 +7,7 @@ use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; -use InvalidArgumentException; +use Stringable; use UnexpectedValueException; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\GettypeResult; @@ -104,9 +104,10 @@ public function dbTypecast(mixed $value): string|ExpressionInterface|null $value instanceof DateTimeImmutable => $this->dbTypecastDateTime($value), $value instanceof DateTimeInterface => $this->dbTypecastDateTime(DateTimeImmutable::createFromInterface($value)), $value instanceof ExpressionInterface => $value, - default => $this->dbTypecastString((string) $value), + $value instanceof Stringable => $this->dbTypecastString((string) $value), + default => $this->throwWrongTypeException($value::class), }, - default => throw new InvalidArgumentException('Wrong ' . gettype($value) . ' value for ' . $this->getType() . ' column.'), + default => $this->throwWrongTypeException(gettype($value)), }; } @@ -169,7 +170,7 @@ protected function getFormat(): string ColumnType::BIGINT => 'U', ColumnType::FLOAT => 'U.u', default => throw new UnexpectedValueException( - 'Unsupported abstract column type "' . $this->getType() . '" for ' . static::class . ' class.', + 'Unsupported abstract column type ' . $this->getType() . ' for ' . static::class . ' class.', ), }; } diff --git a/src/Schema/Column/DoubleColumn.php b/src/Schema/Column/DoubleColumn.php index 479004e6f..f00228029 100644 --- a/src/Schema/Column/DoubleColumn.php +++ b/src/Schema/Column/DoubleColumn.php @@ -4,11 +4,15 @@ namespace Yiisoft\Db\Schema\Column; +use BackedEnum; +use DateTimeInterface; +use Stringable; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\GettypeResult; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Constant\PhpType; -use function is_float; +use function gettype; /** * Represents the metadata for a double column. @@ -19,13 +23,20 @@ class DoubleColumn extends AbstractColumn public function dbTypecast(mixed $value): float|ExpressionInterface|null { - if (is_float($value)) { - return $value; - } - - return match ($value) { - null, '' => null, - default => $value instanceof ExpressionInterface ? $value : (float) $value, + return match (gettype($value)) { + GettypeResult::DOUBLE => $value, + GettypeResult::INTEGER => (float) $value, + GettypeResult::NULL => null, + GettypeResult::STRING => $value === '' ? null : (float) $value, + GettypeResult::BOOLEAN => $value ? 1.0 : 0.0, + GettypeResult::OBJECT => match (true) { + $value instanceof ExpressionInterface => $value, + $value instanceof BackedEnum => (float) $value->value, + $value instanceof DateTimeInterface => (float) $value->format('U.u'), + $value instanceof Stringable => (float)(string) $value, + default => $this->throwWrongTypeException($value::class), + }, + default => $this->throwWrongTypeException(gettype($value)), }; } diff --git a/src/Schema/Column/IntegerColumn.php b/src/Schema/Column/IntegerColumn.php index 9b6025214..eac0d3b41 100644 --- a/src/Schema/Column/IntegerColumn.php +++ b/src/Schema/Column/IntegerColumn.php @@ -5,11 +5,14 @@ namespace Yiisoft\Db\Schema\Column; use BackedEnum; +use DateTimeInterface; +use Stringable; use Yiisoft\Db\Constant\ColumnType; +use Yiisoft\Db\Constant\GettypeResult; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Constant\PhpType; -use function is_int; +use function gettype; /** * Represents the schema for an integer column. @@ -20,17 +23,20 @@ class IntegerColumn extends AbstractColumn public function dbTypecast(mixed $value): int|ExpressionInterface|null { - if (is_int($value)) { - return $value; - } - - return match ($value) { - null, '' => null, - default => match (true) { + return match (gettype($value)) { + GettypeResult::INTEGER => $value, + GettypeResult::NULL => null, + GettypeResult::STRING => $value === '' ? null : (int) $value, + GettypeResult::DOUBLE => (int) $value, + GettypeResult::BOOLEAN => $value ? 1 : 0, + GettypeResult::OBJECT => match (true) { $value instanceof ExpressionInterface => $value, $value instanceof BackedEnum => (int) $value->value, - default => (int) $value, + $value instanceof DateTimeInterface => $value->getTimestamp(), + $value instanceof Stringable => (int)(string) $value, + default => $this->throwWrongTypeException($value::class), }, + default => $this->throwWrongTypeException(gettype($value)), }; } diff --git a/src/Schema/Column/StringColumn.php b/src/Schema/Column/StringColumn.php index db2d0dc2e..d886ad63b 100644 --- a/src/Schema/Column/StringColumn.php +++ b/src/Schema/Column/StringColumn.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Schema\Column; use BackedEnum; +use Stringable; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Constant\GettypeResult; @@ -25,13 +26,16 @@ public function dbTypecast(mixed $value): mixed GettypeResult::STRING => $value, GettypeResult::RESOURCE => $value, GettypeResult::NULL => null, + GettypeResult::INTEGER, + GettypeResult::DOUBLE => (string) $value, GettypeResult::BOOLEAN => $value ? '1' : '0', GettypeResult::OBJECT => match (true) { $value instanceof ExpressionInterface => $value, $value instanceof BackedEnum => (string) $value->value, - default => (string) $value, + $value instanceof Stringable => (string) $value, + default => $this->throwWrongTypeException($value::class), }, - default => (string) $value, + default => $this->throwWrongTypeException(gettype($value)), }; } diff --git a/tests/AbstractColumnTest.php b/tests/AbstractColumnTest.php index f85ab638c..e0a667790 100644 --- a/tests/AbstractColumnTest.php +++ b/tests/AbstractColumnTest.php @@ -4,15 +4,18 @@ namespace Yiisoft\Db\Tests; +use InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; use Yiisoft\Db\Schema\Column\ColumnInterface; +use Yiisoft\Db\Tests\Provider\ColumnProvider; use function is_object; abstract class AbstractColumnTest extends TestCase { - /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::predefinedTypes */ - public function testPredefinedType(string $className, string $type, string $phpType) + #[DataProviderExternal(ColumnProvider::class, 'predefinedTypes')] + public function testPredefinedType(string $className, string $type, string $phpType): void { $column = new $className(); @@ -20,8 +23,8 @@ public function testPredefinedType(string $className, string $type, string $phpT $this->assertSame($phpType, $column->getPhpType()); } - /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::dbTypecastColumns */ - public function testDbTypecastColumns(ColumnInterface $column, array $values) + #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')] + public function testDbTypecastColumns(ColumnInterface $column, array $values): void { // Set the timezone for testing purposes, could be any timezone except UTC $oldDatetime = date_default_timezone_get(); @@ -38,8 +41,19 @@ public function testDbTypecastColumns(ColumnInterface $column, array $values) date_default_timezone_set($oldDatetime); } - /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::phpTypecastColumns */ - public function testPhpTypecastColumns(ColumnInterface $column, array $values) + #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumnsWithException')] + public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value): void + { + $type = is_object($value) ? $value::class : gettype($value); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Wrong $type value for {$column->getType()} column."); + + $column->dbTypecast($value); + } + + #[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')] + public function testPhpTypecastColumns(ColumnInterface $column, array $values): void { foreach ($values as [$expected, $value]) { if (is_object($expected) && !(is_object($value) && $expected::class === $value::class)) { diff --git a/tests/Provider/ColumnProvider.php b/tests/Provider/ColumnProvider.php index d6b8c4b3a..0914a0650 100644 --- a/tests/Provider/ColumnProvider.php +++ b/tests/Provider/ColumnProvider.php @@ -76,6 +76,8 @@ public static function dbTypecastColumns(): array [1, true], [0, false], [1, IntEnum::ONE], + [1, new Stringable('1')], + [1745071895, new DateTimeImmutable('2025-04-19 14:11:35')], [$expression = new Expression('1'), $expression], ], ], @@ -89,7 +91,11 @@ public static function dbTypecastColumns(): array [1, '1'], [1, true], [0, false], + [1, IntEnum::ONE], + [1, new Stringable('1')], + [1745071895, new DateTimeImmutable('2025-04-19 14:11:35')], ['12345678901234567890', '12345678901234567890'], + ['12345678901234567890', new Stringable('12345678901234567890')], [$expression = new Expression('1'), $expression], ], ], @@ -103,6 +109,9 @@ public static function dbTypecastColumns(): array [1.0, '1'], [1.0, true], [0.0, false], + [1.0, IntEnum::ONE], + [1.0, new Stringable('1')], + [1745071895.123456, new DateTimeImmutable('2025-04-19 14:11:35.123456')], [$expression = new Expression('1'), $expression], ], ], @@ -116,6 +125,7 @@ public static function dbTypecastColumns(): array ['0', false], ['string', 'string'], ['one', StringEnum::ONE], + ['string', new Stringable('string')], [$resource = fopen('php://memory', 'rb'), $resource], [$expression = new Expression('expression'), $expression], ], @@ -128,6 +138,8 @@ public static function dbTypecastColumns(): array ['1', true], ['0', false], [new Param("\x10\x11\x12", PDO::PARAM_LOB), "\x10\x11\x12"], + ['one', StringEnum::ONE], + ['string', new Stringable('string')], [$resource = fopen('php://memory', 'rb'), $resource], [$expression = new Expression('expression'), $expression], ], @@ -449,6 +461,29 @@ public static function dbTypecastColumns(): array ]; } + public static function dbTypecastColumnsWithException(): array + { + return [ + 'integer array' => [new IntegerColumn(), []], + 'integer resource' => [new IntegerColumn(), fopen('php://memory', 'r')], + 'integer stdClass' => [new IntegerColumn(), new stdClass()], + 'bigint array' => [new BigintColumn(), []], + 'bigint resource' => [new BigintColumn(), fopen('php://memory', 'r')], + 'bigint stdClass' => [new BigintColumn(), new stdClass()], + 'double array' => [new DoubleColumn(), []], + 'double resource' => [new DoubleColumn(), fopen('php://memory', 'r')], + 'double stdClass' => [new DoubleColumn(), new stdClass()], + 'string array' => [new StringColumn(), []], + 'string stdClass' => [new StringColumn(), new stdClass()], + 'binary array' => [new BinaryColumn(), []], + 'binary stdClass' => [new BinaryColumn(), new stdClass()], + 'datetime array' => [new DateTimeColumn(), []], + 'datetime resource' => [new DateTimeColumn(), fopen('php://memory', 'r')], + 'datetime enum' => [new DateTimeColumn(), StringEnum::ONE], + 'datetime stdClass' => [new DateTimeColumn(), new stdClass()], + ]; + } + public static function phpTypecastColumns(): array { $utcTimezone = new DateTimeZone('UTC'); From d78b713b5604234864444938513bba3ce27a84ca Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 18 May 2025 10:03:10 +0000 Subject: [PATCH 02/14] Apply fixes from StyleCI --- tests/Provider/ColumnProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Provider/ColumnProvider.php b/tests/Provider/ColumnProvider.php index 0914a0650..0cd2e06d7 100644 --- a/tests/Provider/ColumnProvider.php +++ b/tests/Provider/ColumnProvider.php @@ -467,9 +467,9 @@ public static function dbTypecastColumnsWithException(): array 'integer array' => [new IntegerColumn(), []], 'integer resource' => [new IntegerColumn(), fopen('php://memory', 'r')], 'integer stdClass' => [new IntegerColumn(), new stdClass()], - 'bigint array' => [new BigintColumn(), []], - 'bigint resource' => [new BigintColumn(), fopen('php://memory', 'r')], - 'bigint stdClass' => [new BigintColumn(), new stdClass()], + 'bigint array' => [new BigIntColumn(), []], + 'bigint resource' => [new BigIntColumn(), fopen('php://memory', 'r')], + 'bigint stdClass' => [new BigIntColumn(), new stdClass()], 'double array' => [new DoubleColumn(), []], 'double resource' => [new DoubleColumn(), fopen('php://memory', 'r')], 'double stdClass' => [new DoubleColumn(), new stdClass()], From 0d4fbd1bcac23218aa1941e783e14dba69929ed3 Mon Sep 17 00:00:00 2001 From: Tigrov <8563175+Tigrov@users.noreply.github.com> Date: Sun, 18 May 2025 10:22:44 +0000 Subject: [PATCH 03/14] Apply Rector changes (CI) --- tests/AbstractColumnTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/AbstractColumnTest.php b/tests/AbstractColumnTest.php index e0a667790..8571dcc43 100644 --- a/tests/AbstractColumnTest.php +++ b/tests/AbstractColumnTest.php @@ -44,7 +44,7 @@ public function testDbTypecastColumns(ColumnInterface $column, array $values): v #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumnsWithException')] public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value): void { - $type = is_object($value) ? $value::class : gettype($value); + $type = get_debug_type($value); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Wrong $type value for {$column->getType()} column."); From a4463941bcf6d6a9677f7ab0db83ebb33b6ad952 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 18 May 2025 21:59:49 +0700 Subject: [PATCH 04/14] Fix tests --- tests/AbstractColumnTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/AbstractColumnTest.php b/tests/AbstractColumnTest.php index e0a667790..499e43b19 100644 --- a/tests/AbstractColumnTest.php +++ b/tests/AbstractColumnTest.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Tests\Provider\ColumnProvider; +use function gettype; use function is_object; abstract class AbstractColumnTest extends TestCase From 84b39e48292fbc923afe8b2c9a8b539f2ed638f1 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 18 May 2025 15:00:31 +0000 Subject: [PATCH 05/14] Apply fixes from StyleCI --- tests/AbstractColumnTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/AbstractColumnTest.php b/tests/AbstractColumnTest.php index 54b8f4695..8571dcc43 100644 --- a/tests/AbstractColumnTest.php +++ b/tests/AbstractColumnTest.php @@ -10,7 +10,6 @@ use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Tests\Provider\ColumnProvider; -use function gettype; use function is_object; abstract class AbstractColumnTest extends TestCase From 7bfd8a07035fb3f5c3091a10070e643e3335ad39 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 18 May 2025 22:05:12 +0700 Subject: [PATCH 06/14] Fix tests --- rector.php | 2 +- tests/AbstractColumnTest.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rector.php b/rector.php index f6ac9c870..85a39ee3f 100644 --- a/rector.php +++ b/rector.php @@ -11,7 +11,7 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', - __DIR__ . '/tests', + // __DIR__ . '/tests', ]); // register a single rule diff --git a/tests/AbstractColumnTest.php b/tests/AbstractColumnTest.php index 8571dcc43..499e43b19 100644 --- a/tests/AbstractColumnTest.php +++ b/tests/AbstractColumnTest.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Tests\Provider\ColumnProvider; +use function gettype; use function is_object; abstract class AbstractColumnTest extends TestCase @@ -44,7 +45,7 @@ public function testDbTypecastColumns(ColumnInterface $column, array $values): v #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumnsWithException')] public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value): void { - $type = get_debug_type($value); + $type = is_object($value) ? $value::class : gettype($value); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Wrong $type value for {$column->getType()} column."); From 74c6392b4d768dc517133ca6c4ed327c27cc6aef Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 18 May 2025 15:05:26 +0000 Subject: [PATCH 07/14] Apply fixes from StyleCI --- rector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rector.php b/rector.php index 85a39ee3f..d710b2774 100644 --- a/rector.php +++ b/rector.php @@ -11,7 +11,7 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', - // __DIR__ . '/tests', + // __DIR__ . '/tests', ]); // register a single rule From 87bd0bed8ce8220156647c7393997bfe183d0135 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 18 May 2025 22:15:25 +0700 Subject: [PATCH 08/14] Fix psalm --- rector.php | 1 - src/Schema/Column/BigIntColumn.php | 5 ++++- src/Schema/Column/BinaryColumn.php | 2 +- src/Schema/Column/DoubleColumn.php | 1 + src/Schema/Column/IntegerColumn.php | 1 + src/Schema/Column/StringColumn.php | 2 +- 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rector.php b/rector.php index d710b2774..00b3d6c11 100644 --- a/rector.php +++ b/rector.php @@ -11,7 +11,6 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', - // __DIR__ . '/tests', ]); // register a single rule diff --git a/src/Schema/Column/BigIntColumn.php b/src/Schema/Column/BigIntColumn.php index 7927d1187..1ae2ee1d9 100644 --- a/src/Schema/Column/BigIntColumn.php +++ b/src/Schema/Column/BigIntColumn.php @@ -26,7 +26,10 @@ class BigIntColumn extends AbstractColumn public function dbTypecast(mixed $value): int|string|ExpressionInterface|null { - /** @var ExpressionInterface|int|string|null */ + /** + * @var ExpressionInterface|int|string|null + * @psalm-suppress MixedArgument + */ return match (gettype($value)) { GettypeResult::STRING => $this->dbTypecastString($value), GettypeResult::NULL => null, diff --git a/src/Schema/Column/BinaryColumn.php b/src/Schema/Column/BinaryColumn.php index a370af3f5..424cb6c74 100644 --- a/src/Schema/Column/BinaryColumn.php +++ b/src/Schema/Column/BinaryColumn.php @@ -27,7 +27,7 @@ public function dbTypecast(mixed $value): mixed GettypeResult::STRING => new Param($value, PDO::PARAM_LOB), GettypeResult::RESOURCE => $value, GettypeResult::NULL => null, - GettypeResult::INTEGER, + GettypeResult::INTEGER => (string) $value, GettypeResult::DOUBLE => (string) $value, GettypeResult::BOOLEAN => $value ? '1' : '0', GettypeResult::OBJECT => match (true) { diff --git a/src/Schema/Column/DoubleColumn.php b/src/Schema/Column/DoubleColumn.php index f00228029..2bb8d2f17 100644 --- a/src/Schema/Column/DoubleColumn.php +++ b/src/Schema/Column/DoubleColumn.php @@ -23,6 +23,7 @@ class DoubleColumn extends AbstractColumn public function dbTypecast(mixed $value): float|ExpressionInterface|null { + /** @var float|ExpressionInterface|null */ return match (gettype($value)) { GettypeResult::DOUBLE => $value, GettypeResult::INTEGER => (float) $value, diff --git a/src/Schema/Column/IntegerColumn.php b/src/Schema/Column/IntegerColumn.php index eac0d3b41..79b70fd9f 100644 --- a/src/Schema/Column/IntegerColumn.php +++ b/src/Schema/Column/IntegerColumn.php @@ -23,6 +23,7 @@ class IntegerColumn extends AbstractColumn public function dbTypecast(mixed $value): int|ExpressionInterface|null { + /** @var int|ExpressionInterface|null */ return match (gettype($value)) { GettypeResult::INTEGER => $value, GettypeResult::NULL => null, diff --git a/src/Schema/Column/StringColumn.php b/src/Schema/Column/StringColumn.php index d886ad63b..d78dd1cc7 100644 --- a/src/Schema/Column/StringColumn.php +++ b/src/Schema/Column/StringColumn.php @@ -26,7 +26,7 @@ public function dbTypecast(mixed $value): mixed GettypeResult::STRING => $value, GettypeResult::RESOURCE => $value, GettypeResult::NULL => null, - GettypeResult::INTEGER, + GettypeResult::INTEGER => (string) $value, GettypeResult::DOUBLE => (string) $value, GettypeResult::BOOLEAN => $value ? '1' : '0', GettypeResult::OBJECT => match (true) { From 344dd1d33bff5ca8f773ef1e62503302cdae177d Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 18 May 2025 22:17:22 +0700 Subject: [PATCH 09/14] Fix tests --- tests/AbstractColumnTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/AbstractColumnTest.php b/tests/AbstractColumnTest.php index 499e43b19..093abe252 100644 --- a/tests/AbstractColumnTest.php +++ b/tests/AbstractColumnTest.php @@ -16,7 +16,7 @@ abstract class AbstractColumnTest extends TestCase { #[DataProviderExternal(ColumnProvider::class, 'predefinedTypes')] - public function testPredefinedType(string $className, string $type, string $phpType): void + public function testPredefinedType(string $className, string $type, string $phpType) { $column = new $className(); @@ -25,7 +25,7 @@ public function testPredefinedType(string $className, string $type, string $phpT } #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')] - public function testDbTypecastColumns(ColumnInterface $column, array $values): void + public function testDbTypecastColumns(ColumnInterface $column, array $values) { // Set the timezone for testing purposes, could be any timezone except UTC $oldDatetime = date_default_timezone_get(); @@ -43,7 +43,7 @@ public function testDbTypecastColumns(ColumnInterface $column, array $values): v } #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumnsWithException')] - public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value): void + public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value) { $type = is_object($value) ? $value::class : gettype($value); @@ -54,7 +54,7 @@ public function testDbTypecastColumnsWithException(ColumnInterface $column, mixe } #[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')] - public function testPhpTypecastColumns(ColumnInterface $column, array $values): void + public function testPhpTypecastColumns(ColumnInterface $column, array $values) { foreach ($values as [$expected, $value]) { if (is_object($expected) && !(is_object($value) && $expected::class === $value::class)) { From d9e3c73d42df02019b4bc69cf801c54b234b5ce6 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 18 May 2025 15:17:35 +0000 Subject: [PATCH 10/14] Apply fixes from StyleCI --- src/Schema/Column/DoubleColumn.php | 2 +- src/Schema/Column/IntegerColumn.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Schema/Column/DoubleColumn.php b/src/Schema/Column/DoubleColumn.php index 2bb8d2f17..b15ec4be8 100644 --- a/src/Schema/Column/DoubleColumn.php +++ b/src/Schema/Column/DoubleColumn.php @@ -23,7 +23,7 @@ class DoubleColumn extends AbstractColumn public function dbTypecast(mixed $value): float|ExpressionInterface|null { - /** @var float|ExpressionInterface|null */ + /** @var ExpressionInterface|float|null */ return match (gettype($value)) { GettypeResult::DOUBLE => $value, GettypeResult::INTEGER => (float) $value, diff --git a/src/Schema/Column/IntegerColumn.php b/src/Schema/Column/IntegerColumn.php index 79b70fd9f..7c8a9e4f4 100644 --- a/src/Schema/Column/IntegerColumn.php +++ b/src/Schema/Column/IntegerColumn.php @@ -23,7 +23,7 @@ class IntegerColumn extends AbstractColumn public function dbTypecast(mixed $value): int|ExpressionInterface|null { - /** @var int|ExpressionInterface|null */ + /** @var ExpressionInterface|int|null */ return match (gettype($value)) { GettypeResult::INTEGER => $value, GettypeResult::NULL => null, From 8bca775504bed8a8f60adcc864e04b41da5bf78a Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 22 May 2025 09:19:13 +0700 Subject: [PATCH 11/14] Apply suggestions --- src/Schema/Column/BigIntColumn.php | 5 ++++- src/Schema/Column/DoubleColumn.php | 13 ++++++++----- src/Schema/Column/IntegerColumn.php | 4 ++-- tests/Provider/ColumnProvider.php | 14 ++++++++++++-- tests/Support/StringEnum.php | 1 + 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/Schema/Column/BigIntColumn.php b/src/Schema/Column/BigIntColumn.php index 1ae2ee1d9..5df118338 100644 --- a/src/Schema/Column/BigIntColumn.php +++ b/src/Schema/Column/BigIntColumn.php @@ -13,6 +13,7 @@ use Yiisoft\Db\Constant\PhpType; use function gettype; +use function is_int; use const PHP_INT_MAX; use const PHP_INT_MIN; @@ -38,7 +39,9 @@ public function dbTypecast(mixed $value): int|string|ExpressionInterface|null GettypeResult::BOOLEAN => $value ? 1 : 0, GettypeResult::OBJECT => match (true) { $value instanceof ExpressionInterface => $value, - $value instanceof BackedEnum => $this->dbTypecastString((string) $value->value), + $value instanceof BackedEnum => is_int($value->value) + ? $value->value + : $this->dbTypecastString($value->value), $value instanceof DateTimeInterface => $value->getTimestamp(), $value instanceof Stringable => $this->dbTypecastString((string) $value), default => $this->throwWrongTypeException($value::class), diff --git a/src/Schema/Column/DoubleColumn.php b/src/Schema/Column/DoubleColumn.php index b15ec4be8..31d348a10 100644 --- a/src/Schema/Column/DoubleColumn.php +++ b/src/Schema/Column/DoubleColumn.php @@ -13,6 +13,7 @@ use Yiisoft\Db\Constant\PhpType; use function gettype; +use function is_int; /** * Represents the metadata for a double column. @@ -21,20 +22,22 @@ class DoubleColumn extends AbstractColumn { protected const DEFAULT_TYPE = ColumnType::DOUBLE; - public function dbTypecast(mixed $value): float|ExpressionInterface|null + public function dbTypecast(mixed $value): ExpressionInterface|float|int|null { - /** @var ExpressionInterface|float|null */ + /** @var ExpressionInterface|float|int|null */ return match (gettype($value)) { GettypeResult::DOUBLE => $value, - GettypeResult::INTEGER => (float) $value, + GettypeResult::INTEGER => $value, GettypeResult::NULL => null, GettypeResult::STRING => $value === '' ? null : (float) $value, GettypeResult::BOOLEAN => $value ? 1.0 : 0.0, GettypeResult::OBJECT => match (true) { $value instanceof ExpressionInterface => $value, - $value instanceof BackedEnum => (float) $value->value, + $value instanceof BackedEnum => $value->value === '' + ? null + : (is_int($value->value) ? $value->value : (float) $value->value), $value instanceof DateTimeInterface => (float) $value->format('U.u'), - $value instanceof Stringable => (float)(string) $value, + $value instanceof Stringable => ($val = (string) $value) === '' ? null : (float) $val, default => $this->throwWrongTypeException($value::class), }, default => $this->throwWrongTypeException(gettype($value)), diff --git a/src/Schema/Column/IntegerColumn.php b/src/Schema/Column/IntegerColumn.php index 7c8a9e4f4..ac03c82c8 100644 --- a/src/Schema/Column/IntegerColumn.php +++ b/src/Schema/Column/IntegerColumn.php @@ -32,9 +32,9 @@ public function dbTypecast(mixed $value): int|ExpressionInterface|null GettypeResult::BOOLEAN => $value ? 1 : 0, GettypeResult::OBJECT => match (true) { $value instanceof ExpressionInterface => $value, - $value instanceof BackedEnum => (int) $value->value, + $value instanceof BackedEnum => $value->value === '' ? null : (int) $value->value, $value instanceof DateTimeInterface => $value->getTimestamp(), - $value instanceof Stringable => (int)(string) $value, + $value instanceof Stringable => ($val = (string) $value) === '' ? null : (int) $val, default => $this->throwWrongTypeException($value::class), }, default => $this->throwWrongTypeException(gettype($value)), diff --git a/tests/Provider/ColumnProvider.php b/tests/Provider/ColumnProvider.php index 0cd2e06d7..b80f99105 100644 --- a/tests/Provider/ColumnProvider.php +++ b/tests/Provider/ColumnProvider.php @@ -75,7 +75,9 @@ public static function dbTypecastColumns(): array [1, '1'], [1, true], [0, false], + [null, StringEnum::EMPTY], [1, IntEnum::ONE], + [null, new Stringable('')], [1, new Stringable('1')], [1745071895, new DateTimeImmutable('2025-04-19 14:11:35')], [$expression = new Expression('1'), $expression], @@ -91,7 +93,9 @@ public static function dbTypecastColumns(): array [1, '1'], [1, true], [0, false], + [null, StringEnum::EMPTY], [1, IntEnum::ONE], + [null, new Stringable('')], [1, new Stringable('1')], [1745071895, new DateTimeImmutable('2025-04-19 14:11:35')], ['12345678901234567890', '12345678901234567890'], @@ -105,11 +109,13 @@ public static function dbTypecastColumns(): array [null, null], [null, ''], [1.0, 1.0], - [1.0, 1], + [1, 1], [1.0, '1'], [1.0, true], [0.0, false], - [1.0, IntEnum::ONE], + [null, StringEnum::EMPTY], + [1, IntEnum::ONE], + [null, new Stringable('')], [1.0, new Stringable('1')], [1745071895.123456, new DateTimeImmutable('2025-04-19 14:11:35.123456')], [$expression = new Expression('1'), $expression], @@ -124,7 +130,10 @@ public static function dbTypecastColumns(): array ['1', true], ['0', false], ['string', 'string'], + ['1', IntEnum::ONE], + ['', StringEnum::EMPTY], ['one', StringEnum::ONE], + ['', new Stringable('')], ['string', new Stringable('string')], [$resource = fopen('php://memory', 'rb'), $resource], [$expression = new Expression('expression'), $expression], @@ -138,6 +147,7 @@ public static function dbTypecastColumns(): array ['1', true], ['0', false], [new Param("\x10\x11\x12", PDO::PARAM_LOB), "\x10\x11\x12"], + ['1', IntEnum::ONE], ['one', StringEnum::ONE], ['string', new Stringable('string')], [$resource = fopen('php://memory', 'rb'), $resource], diff --git a/tests/Support/StringEnum.php b/tests/Support/StringEnum.php index bbb3ea5bb..b19ea69f4 100644 --- a/tests/Support/StringEnum.php +++ b/tests/Support/StringEnum.php @@ -6,6 +6,7 @@ enum StringEnum: string { + case EMPTY = ''; case ONE = 'one'; case TWO = 'two'; case THREE = 'three'; From 1e512f7af4938dfa5a1ad98024a4edde9cad8e59 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 22 May 2025 09:23:33 +0700 Subject: [PATCH 12/14] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ee042a5..08f9c9a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - Enh #806, #964: Build `Expression` instances inside `Expression::$params` when build a query using `QueryBuilder` (@Tigrov) - Enh #766: Allow `ColumnInterface` as column type. (@Tigrov) - Bug #828: Fix `float` type when use `AbstractCommand::getRawSql()` method (@Tigrov) -- New #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns +- New #752, #974: Implement `ColumnSchemaInterface` classes according to the data type of database table columns for type casting performance (@Tigrov) - Enh #829: Rename `batchInsert()` to `insertBatch()` in `DMLQueryBuilderInterface` and `CommandInterface` and change parameters from `$table, $columns, $rows` to `$table, $rows, $columns = []` (@Tigrov) From 2e504a530b825742f135513f18884114784d9841 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 22 May 2025 10:07:13 +0700 Subject: [PATCH 13/14] Fix rector --- rector.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rector.php b/rector.php index 00b3d6c11..ffdfc6c47 100644 --- a/rector.php +++ b/rector.php @@ -4,6 +4,7 @@ use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; +use Rector\Php80\Rector\Ternary\GetDebugTypeRector; use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; use Rector\Set\ValueObject\LevelSetList; @@ -11,6 +12,7 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', + __DIR__ . '/tests', ]); // register a single rule @@ -22,6 +24,9 @@ ]); $rectorConfig->skip([ + GetDebugTypeRector::class => [ + __DIR__ . '/tests/AbstractColumnTest.php', + ], ReadOnlyPropertyRector::class, NullToStrictStringFuncCallArgRector::class, ]); From 924ed2bd8b57cd45c76be1577bed91c63344b9c9 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 22 May 2025 10:23:50 +0700 Subject: [PATCH 14/14] Fix tests --- tests/Provider/CommandProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 190355eff..8c39e868f 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -291,7 +291,7 @@ public static function batchInsert(): array ':qp2' => 'test string', ':qp3' => true, ':qp4' => 0, - ':qp5' => 0.0, + ':qp5' => 0, ':qp6' => 'test string2', ':qp7' => false, ], @@ -352,7 +352,7 @@ public static function batchInsert(): array ), 'expectedParams' => [ ':exp1' => 42, - ':qp1' => 1.0, + ':qp1' => 1, ':qp2' => 'test', ':qp3' => false, ],