From ae3fb248d179a98c13f507987acc92866abcb4c1 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 4 Jul 2023 23:18:55 +0700 Subject: [PATCH 01/16] Typecast refactoring --- .gitignore | 4 +- src/ColumnSchema.php | 40 ++++++-------- src/Schema.php | 92 +++++++++++++++---------------- tests/Provider/SchemaProvider.php | 6 +- tests/SchemaTest.php | 3 +- tests/Support/Fixture/mysql.sql | 2 +- 6 files changed, 71 insertions(+), 76 deletions(-) diff --git a/.gitignore b/.gitignore index c33d11d17..61ac953dd 100644 --- a/.gitignore +++ b/.gitignore @@ -31,8 +31,10 @@ composer.phar # phpunit itself is not needed phpunit.phar -# local phpunit config + +# local phpunit config and cache /phpunit.xml +/.phpunit.result.cache # ignore dev installed apps and extensions /apps diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 529fb05da..523b63889 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -51,15 +51,14 @@ final class ColumnSchema extends AbstractColumnSchema */ public function phpTypecast(mixed $value): mixed { - if ($value === null) { - return null; - } - - if ($this->getType() === SchemaInterface::TYPE_JSON) { - return json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR); - } - - return parent::phpTypecast($value); + return match (true) { + $value === null + => null, + $this->getType() === SchemaInterface::TYPE_JSON + => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), + default + => parent::phpTypecast($value), + }; } /** @@ -74,18 +73,15 @@ public function phpTypecast(mixed $value): mixed */ public function dbTypecast(mixed $value): mixed { - if ($value === null) { - return null; - } - - if ($value instanceof ExpressionInterface) { - return $value; - } - - if ($this->getDbType() === SchemaInterface::TYPE_JSON) { - return new JsonExpression($value, $this->getType()); - } - - return $this->typecast($value); + return match (true) { + $value === null + => null, + $value instanceof ExpressionInterface + => $value, + $this->getType() === SchemaInterface::TYPE_JSON + => new JsonExpression($value, $this->getDbType()), + default + => parent::dbTypecast($value), + }; } } diff --git a/src/Schema.php b/src/Schema.php index 8837a9232..acaf07b4d 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -488,13 +488,6 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface $column->unsigned(stripos($dbType, 'unsigned') !== false); $column->type(self::TYPE_STRING); - $extra = $info['extra']; - - if (str_starts_with($extra, 'DEFAULT_GENERATED')) { - $extra = strtoupper(substr($extra, 18)); - } - $column->extra(trim($extra)); - if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $dbType, $matches)) { $type = strtolower($matches[1]); @@ -533,55 +526,58 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface } } - $column->phpType($this->getColumnPhpType($column)); - - if (!$column->isPrimaryKey()) { - // Chapter 2: crutches for MariaDB {@see https://github.com/yiisoft/yii2/issues/19747} - /** @psalm-var string $columnCategory */ - $columnCategory = $this->createColumn( + // Chapter 2: crutches for MariaDB {@see https://github.com/yiisoft/yii2/issues/19747} + if ( + empty($info['extra']) + && !empty($info['extra_default_value']) + && !str_starts_with($info['extra_default_value'], '\'') + && in_array($this->createColumn( $column->getType(), $column->getSize() - )->getCategoryMap()[$column->getType()] ?? ''; - $defaultValue = $info['extra_default_value'] ?? ''; - - if ( - empty($info['extra']) && - !empty($defaultValue) && - in_array($columnCategory, [ - AbstractColumn::TYPE_CATEGORY_STRING, - AbstractColumn::TYPE_CATEGORY_TIME, - ], true) - && !str_starts_with($defaultValue, '\'') - ) { - $info['extra'] = 'DEFAULT_GENERATED'; - } + )->getCategoryMap()[$column->getType()], [ + AbstractColumn::TYPE_CATEGORY_STRING, + AbstractColumn::TYPE_CATEGORY_TIME, + ], true) + ) { + $info['extra'] = 'DEFAULT_GENERATED'; + } - /** - * When displayed in the INFORMATION_SCHEMA.COLUMNS table, a default CURRENT TIMESTAMP is displayed - * as CURRENT_TIMESTAMP up until MariaDB 10.2.2, and as current_timestamp() from MariaDB 10.2.3. - * - * See details here: https://mariadb.com/kb/en/library/now/#description - */ - if ( - in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME], true) - && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', (string) $info['default'], $matches) - ) { - $column->defaultValue(new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) - ? '(' . $matches[1] . ')' : ''))); - } elseif (!empty($info['extra']) && !empty($info['default'])) { - $column->defaultValue(new Expression($info['default'])); - } elseif (isset($type) && $type === 'bit' && $column->getType() !== self::TYPE_BOOLEAN) { - $column->defaultValue(bindec(trim((string) $info['default'], 'b\''))); - } else { - $column->defaultValue($column->phpTypecast($info['default'])); - } - } elseif ($info['default'] !== null) { - $column->defaultValue($column->phpTypecast($info['default'])); + $column->extra($info['extra']); + $column->phpType($this->getColumnPhpType($column)); + $column->defaultValue($this->normalizeDefaultValue($info['default'], $column)); + + if (str_starts_with($column->getExtra(), 'DEFAULT_GENERATED')) { + $column->extra(trim(strtoupper(substr($column->getExtra(), 18)))); } return $column; } + /** + * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database. + * + * @param string|null $defaultValue The default value retrieved from the database. + * @param ColumnSchemaInterface $columnSchema The column schema object. + * + * @return mixed The normalized default value. + */ + private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $columnSchema): mixed + { + return match (true) { + $defaultValue === null + => null, + $columnSchema->isPrimaryKey() + => $columnSchema->phpTypecast($defaultValue), + !empty($columnSchema->getExtra()) + && str_starts_with($columnSchema->getExtra(), 'DEFAULT_GENERATED') + => new Expression($defaultValue), + str_starts_with(strtolower($columnSchema->getDbType()), 'bit') + => $columnSchema->phpTypecast(bindec(trim($defaultValue, "b'"))), + default + => $columnSchema->phpTypecast($defaultValue), + }; + } + /** * Loads all check constraints for the given table. * diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index 38c1710f9..960578df3 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -303,7 +303,7 @@ public static function columnsTypeBit(): array 'size' => 1, 'precision' => 1, 'scale' => null, - 'defaultValue' => null, + 'defaultValue' => false, ], 'bit_col_2' => [ 'type' => 'boolean', @@ -329,7 +329,7 @@ public static function columnsTypeBit(): array 'size' => 32, 'precision' => 32, 'scale' => null, - 'defaultValue' => 0, + 'defaultValue' => null, ], 'bit_col_4' => [ 'type' => 'integer', @@ -355,7 +355,7 @@ public static function columnsTypeBit(): array 'size' => 64, 'precision' => 64, 'scale' => null, - 'defaultValue' => 0, + 'defaultValue' => null, ], 'bit_col_6' => [ 'type' => 'bigint', diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index ba6aed10c..ba2163e89 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -61,13 +61,14 @@ public function testAlternativeDisplayOfDefaultCurrentTimestampInMariaDB(): void 'key' => '', 'default' => 'current_timestamp()', 'extra' => '', + 'extra_default_value' => 'current_timestamp()', 'privileges' => 'select,insert,update,references', 'comment' => '', ]]); $this->assertInstanceOf(ColumnSchema::class, $column); $this->assertInstanceOf(Expression::class, $column->getDefaultValue()); - $this->assertEquals('CURRENT_TIMESTAMP', $column->getDefaultValue()); + $this->assertEquals('current_timestamp()', $column->getDefaultValue()); } /** diff --git a/tests/Support/Fixture/mysql.sql b/tests/Support/Fixture/mysql.sql index 9c791e9dd..92ea625b3 100644 --- a/tests/Support/Fixture/mysql.sql +++ b/tests/Support/Fixture/mysql.sql @@ -167,7 +167,7 @@ CREATE TABLE `type` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `type_bit` ( - `bit_col_1` BIT(1) NOT NULL, + `bit_col_1` BIT(1) NOT NULL DEFAULT b'0', `bit_col_2` BIT(1) DEFAULT b'1', `bit_col_3` BIT(32) NOT NULL, `bit_col_4` BIT(32) DEFAULT b'10000010', From c7d0789a4a88a95ee5d92d6bac34b95b6b3d3e8e Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 6 Jul 2023 11:26:59 +0700 Subject: [PATCH 02/16] Fix test issues --- src/ColumnSchema.php | 4 ++-- src/Schema.php | 25 +++++++++++++++++-------- tests/SchemaTest.php | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 523b63889..4cf570162 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -57,7 +57,7 @@ public function phpTypecast(mixed $value): mixed $this->getType() === SchemaInterface::TYPE_JSON => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), default - => parent::phpTypecast($value), + => parent::phpTypecast($value), }; } @@ -81,7 +81,7 @@ public function dbTypecast(mixed $value): mixed $this->getType() === SchemaInterface::TYPE_JSON => new JsonExpression($value, $this->getDbType()), default - => parent::dbTypecast($value), + => parent::dbTypecast($value), }; } } diff --git a/src/Schema.php b/src/Schema.php index acaf07b4d..cf2ce54b7 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -527,8 +527,9 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface } // Chapter 2: crutches for MariaDB {@see https://github.com/yiisoft/yii2/issues/19747} + $extra = $info['extra']; if ( - empty($info['extra']) + empty($extra) && !empty($info['extra_default_value']) && !str_starts_with($info['extra_default_value'], '\'') && in_array($this->createColumn( @@ -539,15 +540,15 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface AbstractColumn::TYPE_CATEGORY_TIME, ], true) ) { - $info['extra'] = 'DEFAULT_GENERATED'; + $extra = 'DEFAULT_GENERATED'; } - $column->extra($info['extra']); + $column->extra($extra); $column->phpType($this->getColumnPhpType($column)); $column->defaultValue($this->normalizeDefaultValue($info['default'], $column)); - if (str_starts_with($column->getExtra(), 'DEFAULT_GENERATED')) { - $column->extra(trim(strtoupper(substr($column->getExtra(), 18)))); + if (str_starts_with($extra, 'DEFAULT_GENERATED')) { + $column->extra(trim(strtoupper(substr($extra, 18)))); } return $column; @@ -568,13 +569,21 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf => null, $columnSchema->isPrimaryKey() => $columnSchema->phpTypecast($defaultValue), + in_array($columnSchema->getType(), [ + self::TYPE_TIMESTAMP, + self::TYPE_DATETIME, + self::TYPE_DATE, + self::TYPE_TIME + ], true) + && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 + => new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')), !empty($columnSchema->getExtra()) - && str_starts_with($columnSchema->getExtra(), 'DEFAULT_GENERATED') + && !empty($defaultValue) => new Expression($defaultValue), - str_starts_with(strtolower($columnSchema->getDbType()), 'bit') + str_starts_with(strtolower((string) $columnSchema->getDbType()), 'bit') => $columnSchema->phpTypecast(bindec(trim($defaultValue, "b'"))), default - => $columnSchema->phpTypecast($defaultValue), + => $columnSchema->phpTypecast($defaultValue), }; } diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index ba2163e89..ce095a4a3 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -68,7 +68,7 @@ public function testAlternativeDisplayOfDefaultCurrentTimestampInMariaDB(): void $this->assertInstanceOf(ColumnSchema::class, $column); $this->assertInstanceOf(Expression::class, $column->getDefaultValue()); - $this->assertEquals('current_timestamp()', $column->getDefaultValue()); + $this->assertEquals('CURRENT_TIMESTAMP', $column->getDefaultValue()); } /** From a5492893a28969d444d1a92d31ced6164aab711e Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 6 Jul 2023 12:05:30 +0700 Subject: [PATCH 03/16] Fix test issues --- src/Schema.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index cf2ce54b7..ea37dd286 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -535,7 +535,7 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface && in_array($this->createColumn( $column->getType(), $column->getSize() - )->getCategoryMap()[$column->getType()], [ + )->getCategoryMap()[$column->getType()] ?? null, [ AbstractColumn::TYPE_CATEGORY_STRING, AbstractColumn::TYPE_CATEGORY_TIME, ], true) @@ -573,7 +573,7 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf self::TYPE_TIMESTAMP, self::TYPE_DATETIME, self::TYPE_DATE, - self::TYPE_TIME + self::TYPE_TIME, ], true) && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 => new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')), From bd0f3f9f57fbb5d4c9c1afca9cd6e98327b02418 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 6 Jul 2023 22:38:52 +0700 Subject: [PATCH 04/16] Update --- src/ColumnSchema.php | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 4cf570162..28f34a73d 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -39,49 +39,48 @@ final class ColumnSchema extends AbstractColumnSchema { /** - * Converts the input value according to {@see phpType} after retrieval from the database. + * Converts the input value according to {@see type} and {@see dbType} for use in a db query. * * If the value is `null` or an {@see \Yiisoft\Db\Expression\Expression}, it won't be converted. * * @param mixed $value The value to convert. * - * @throws JsonException If the value can't be decoded. - * - * @return mixed The converted value. + * @return mixed The converted value. This may also be an array containing the value as the first element and the + * PDO type as the second element. */ - public function phpTypecast(mixed $value): mixed + public function dbTypecast(mixed $value): mixed { return match (true) { - $value === null - => null, + $value === null, + $value instanceof ExpressionInterface + => $value, $this->getType() === SchemaInterface::TYPE_JSON - => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), + => new JsonExpression($value, $this->getDbType()), default - => parent::phpTypecast($value), + => parent::dbTypecast($value), }; } /** - * Converts the input value according to {@see type} and {@see dbType} for use in a db query. + * Converts the input value according to {@see phpType} after retrieval from the database. * * If the value is `null` or an {@see \Yiisoft\Db\Expression\Expression}, it won't be converted. * * @param mixed $value The value to convert. * - * @return mixed The converted value. This may also be an array containing the value as the first element and the - * PDO type as the second element. + * @throws JsonException If the value can't be decoded. + * + * @return mixed The converted value. */ - public function dbTypecast(mixed $value): mixed + public function phpTypecast(mixed $value): mixed { return match (true) { $value === null => null, - $value instanceof ExpressionInterface - => $value, $this->getType() === SchemaInterface::TYPE_JSON - => new JsonExpression($value, $this->getDbType()), + => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), default - => parent::dbTypecast($value), + => parent::phpTypecast($value), }; } } From 5316b8307b1bbacf9458b3fe16cb848cf83e6333 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 7 Jul 2023 11:46:24 +0700 Subject: [PATCH 05/16] Rename `$columnSchema` to `$column` --- src/Schema.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index ea37dd286..e337cf268 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -558,18 +558,18 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database. * * @param string|null $defaultValue The default value retrieved from the database. - * @param ColumnSchemaInterface $columnSchema The column schema object. + * @param ColumnSchemaInterface $column The column schema object. * * @return mixed The normalized default value. */ - private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $columnSchema): mixed + private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed { return match (true) { $defaultValue === null => null, - $columnSchema->isPrimaryKey() - => $columnSchema->phpTypecast($defaultValue), - in_array($columnSchema->getType(), [ + $column->isPrimaryKey() + => $column->phpTypecast($defaultValue), + in_array($column->getType(), [ self::TYPE_TIMESTAMP, self::TYPE_DATETIME, self::TYPE_DATE, @@ -577,13 +577,13 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf ], true) && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 => new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')), - !empty($columnSchema->getExtra()) + !empty($column->getExtra()) && !empty($defaultValue) => new Expression($defaultValue), - str_starts_with(strtolower((string) $columnSchema->getDbType()), 'bit') - => $columnSchema->phpTypecast(bindec(trim($defaultValue, "b'"))), + str_starts_with(strtolower((string) $column->getDbType()), 'bit') + => $column->phpTypecast(bindec(trim($defaultValue, "b'"))), default - => $columnSchema->phpTypecast($defaultValue), + => $column->phpTypecast($defaultValue), }; } From d9151b22e647ab7b5dec77ca144a1b4d68e2d090 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 7 Jul 2023 12:24:00 +0700 Subject: [PATCH 06/16] $info['type'] is always string --- src/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Schema.php b/src/Schema.php index e337cf268..0c6ce71ae 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -475,7 +475,7 @@ protected function getCreateTableSql(TableSchemaInterface $table): string */ protected function loadColumnSchema(array $info): ColumnSchemaInterface { - $dbType = $info['type'] ?? ''; + $dbType = $info['type']; $column = $this->createColumnSchema($info['field']); From 1c6d43d62b07d5862f303d785b589213badbf2d8 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 8 Jul 2023 21:18:54 +0700 Subject: [PATCH 07/16] Update --- src/Schema.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 0c6ce71ae..723b63f1e 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -532,12 +532,9 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface empty($extra) && !empty($info['extra_default_value']) && !str_starts_with($info['extra_default_value'], '\'') - && in_array($this->createColumn( - $column->getType(), - $column->getSize() - )->getCategoryMap()[$column->getType()] ?? null, [ - AbstractColumn::TYPE_CATEGORY_STRING, - AbstractColumn::TYPE_CATEGORY_TIME, + && in_array($column->getType(), [ + self::TYPE_CHAR, self::TYPE_STRING, self::TYPE_TEXT, + self::TYPE_DATETIME, self::TYPE_TIMESTAMP, self::TYPE_TIME, self::TYPE_DATE, ], true) ) { $extra = 'DEFAULT_GENERATED'; From f94dff791ab33e396eba02d44ad155c4bc2070c8 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 8 Jul 2023 21:20:01 +0700 Subject: [PATCH 08/16] Update --- src/Schema.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Schema.php b/src/Schema.php index 723b63f1e..353560371 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -15,7 +15,6 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Helper\DbArrayHelper; -use Yiisoft\Db\Schema\Builder\AbstractColumn; use Yiisoft\Db\Schema\Builder\ColumnInterface; use Yiisoft\Db\Schema\ColumnSchemaInterface; use Yiisoft\Db\Schema\TableSchemaInterface; From 8c14fcc0f54cd5940d44f1b59d04bef1355455c1 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 9 Jul 2023 11:02:58 +0700 Subject: [PATCH 09/16] Keep methods order --- src/ColumnSchema.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 28f34a73d..0f0edebcc 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -39,48 +39,48 @@ final class ColumnSchema extends AbstractColumnSchema { /** - * Converts the input value according to {@see type} and {@see dbType} for use in a db query. + * Converts the input value according to {@see phpType} after retrieval from the database. * * If the value is `null` or an {@see \Yiisoft\Db\Expression\Expression}, it won't be converted. * * @param mixed $value The value to convert. * - * @return mixed The converted value. This may also be an array containing the value as the first element and the - * PDO type as the second element. + * @throws JsonException If the value can't be decoded. + * + * @return mixed The converted value. */ - public function dbTypecast(mixed $value): mixed + public function phpTypecast(mixed $value): mixed { return match (true) { - $value === null, - $value instanceof ExpressionInterface - => $value, + $value === null + => null, $this->getType() === SchemaInterface::TYPE_JSON - => new JsonExpression($value, $this->getDbType()), + => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), default - => parent::dbTypecast($value), + => parent::phpTypecast($value), }; } /** - * Converts the input value according to {@see phpType} after retrieval from the database. + * Converts the input value according to {@see type} and {@see dbType} for use in a db query. * * If the value is `null` or an {@see \Yiisoft\Db\Expression\Expression}, it won't be converted. * * @param mixed $value The value to convert. * - * @throws JsonException If the value can't be decoded. - * - * @return mixed The converted value. + * @return mixed The converted value. This may also be an array containing the value as the first element and the + * PDO type as the second element. */ - public function phpTypecast(mixed $value): mixed + public function dbTypecast(mixed $value): mixed { return match (true) { - $value === null - => null, + $value === null, + $value instanceof ExpressionInterface + => $value, $this->getType() === SchemaInterface::TYPE_JSON - => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), + => new JsonExpression($value, $this->getDbType()), default - => parent::phpTypecast($value), + => parent::dbTypecast($value), }; } } From 72777e6f3107a0fbf91ff340c4bf5ba333c7031a Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 10 Jul 2023 09:39:49 +0700 Subject: [PATCH 10/16] Update --- CHANGELOG.md | 4 ++-- src/ColumnSchema.php | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03bd34654..64531c30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ## 1.0.1 under development -- no changes in this release. +- Enh #295: Typecast refactoring (@Tigrov) ## 1.0.0 April 12, 2023 -- Initial release. \ No newline at end of file +- Initial release. diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 0f0edebcc..88e0bfcdf 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -52,12 +52,10 @@ final class ColumnSchema extends AbstractColumnSchema public function phpTypecast(mixed $value): mixed { return match (true) { - $value === null - => null, + $value === null => null, $this->getType() === SchemaInterface::TYPE_JSON => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), - default - => parent::phpTypecast($value), + default => parent::phpTypecast($value), }; } @@ -74,13 +72,11 @@ public function phpTypecast(mixed $value): mixed public function dbTypecast(mixed $value): mixed { return match (true) { - $value === null, - $value instanceof ExpressionInterface - => $value, + $value === null => null, + $value instanceof ExpressionInterface => $value, $this->getType() === SchemaInterface::TYPE_JSON => new JsonExpression($value, $this->getDbType()), - default - => parent::dbTypecast($value), + default => parent::dbTypecast($value), }; } } From 6fe8eb74df93d19b6dddd02081dc8a112b03205f Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 11:20:30 +0700 Subject: [PATCH 11/16] Add EOLs --- src/ColumnSchema.php | 31 ++++++++++++++++++------------- src/Schema.php | 29 ++++++++++++----------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 88e0bfcdf..90d5852cb 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -51,12 +51,15 @@ final class ColumnSchema extends AbstractColumnSchema */ public function phpTypecast(mixed $value): mixed { - return match (true) { - $value === null => null, - $this->getType() === SchemaInterface::TYPE_JSON - => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), - default => parent::phpTypecast($value), - }; + if ($value === null) { + return null; + } + + if ($this->getType() === SchemaInterface::TYPE_JSON) { + return json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR); + } + + return parent::phpTypecast($value); } /** @@ -71,12 +74,14 @@ public function phpTypecast(mixed $value): mixed */ public function dbTypecast(mixed $value): mixed { - return match (true) { - $value === null => null, - $value instanceof ExpressionInterface => $value, - $this->getType() === SchemaInterface::TYPE_JSON - => new JsonExpression($value, $this->getDbType()), - default => parent::dbTypecast($value), - }; + if ($value === null || $value instanceof ExpressionInterface) { + return $value; + } + + if ($this->getType() === SchemaInterface::TYPE_JSON) { + return new JsonExpression($value, $this->getDbType()); + } + + return parent::dbTypecast($value); } } diff --git a/src/Schema.php b/src/Schema.php index 353560371..0e51ac7aa 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -561,25 +561,20 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed { return match (true) { - $defaultValue === null - => null, - $column->isPrimaryKey() - => $column->phpTypecast($defaultValue), - in_array($column->getType(), [ - self::TYPE_TIMESTAMP, - self::TYPE_DATETIME, - self::TYPE_DATE, - self::TYPE_TIME, - ], true) - && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 - => new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')), - !empty($column->getExtra()) - && !empty($defaultValue) - => new Expression($defaultValue), + $defaultValue === null => null, + + $column->isPrimaryKey() => $column->phpTypecast($defaultValue), + + in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME], true) + && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 + => new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')), + + !empty($column->getExtra()) && !empty($defaultValue) => new Expression($defaultValue), + str_starts_with(strtolower((string) $column->getDbType()), 'bit') => $column->phpTypecast(bindec(trim($defaultValue, "b'"))), - default - => $column->phpTypecast($defaultValue), + + default => $column->phpTypecast($defaultValue), }; } From 42985da211362162770800b616558ee241e15009 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 11:45:06 +0700 Subject: [PATCH 12/16] Add tests --- tests/ColumnSchemaTest.php | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index 17e4e7261..704ba9460 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -62,4 +62,52 @@ public function testPhpTypeCastJson(): void $this->assertSame(['a' => 1], $columnSchema->phpTypeCast('{"a":1}')); } + + public function testPhpTypeCast(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + $tableSchema = $schema->getTableSchema('type'); + + $command->insert( + 'type', + [ + 'int_col' => 1, + 'char_col' => str_repeat('x', 100), + 'char_col3' => null, + 'float_col' => 1.234, + 'blob_col' => "\x10\x11\x12", + 'time' => '2023-07-11 14:50:23', + 'bool_col' => false, + 'bit_col' => 0b0110_0100, // 100 + 'json_col' => [['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], + ] + ); + $command->execute(); + $query = (new Query($db))->from('type')->one(); + + $this->assertNotNull($tableSchema); + + $intColPhpType = $tableSchema->getColumn('int_col')?->phpTypecast($query['int_col']); + $charColPhpType = $tableSchema->getColumn('char_col')?->phpTypecast($query['char_col']); + $floatColPhpType = $tableSchema->getColumn('float_col')?->phpTypecast($query['float_col']); + $blobColPhpType = $tableSchema->getColumn('blob_col')?->phpTypecast($query['blob_col']); + $timePhpType = $tableSchema->getColumn('time')?->phpTypecast($query['time']); + $boolColPhpType = $tableSchema->getColumn('bool_col')?->phpTypecast($query['bool_col']); + $bitColPhpType = $tableSchema->getColumn('bit_col')?->phpTypecast($query['bit_col']); + $jsonColPhpType = $tableSchema->getColumn('json_col')?->phpTypecast($query['json_col']); + + $this->assertSame(1, $intColPhpType); + $this->assertSame(str_repeat('x', 100), $charColPhpType); + $this->assertSame(1.234, $floatColPhpType); + $this->assertSame("\x10\x11\x12", $blobColPhpType); + $this->assertSame('2023-07-11 14:50:23', $timePhpType); + $this->assertFalse($boolColPhpType); + $this->assertSame(0b0110_0100, $bitColPhpType); + $this->assertSame([['a' => 1, 'b' => null, 'c' => [1, 3, 5]]], $jsonColPhpType); + + $db->close(); + } } From 2508ce191376e2f6676767ab1fba2b64c41cb609 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 11:55:02 +0700 Subject: [PATCH 13/16] Add tests --- tests/ColumnSchemaTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index 704ba9460..7dc66640f 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -92,6 +92,7 @@ public function testPhpTypeCast(): void $intColPhpType = $tableSchema->getColumn('int_col')?->phpTypecast($query['int_col']); $charColPhpType = $tableSchema->getColumn('char_col')?->phpTypecast($query['char_col']); + $charCol3PhpType = $tableSchema->getColumn('char_col')?->phpTypecast($query['char_col3']); $floatColPhpType = $tableSchema->getColumn('float_col')?->phpTypecast($query['float_col']); $blobColPhpType = $tableSchema->getColumn('blob_col')?->phpTypecast($query['blob_col']); $timePhpType = $tableSchema->getColumn('time')?->phpTypecast($query['time']); @@ -101,6 +102,7 @@ public function testPhpTypeCast(): void $this->assertSame(1, $intColPhpType); $this->assertSame(str_repeat('x', 100), $charColPhpType); + $this->assertNull($charCol3PhpType); $this->assertSame(1.234, $floatColPhpType); $this->assertSame("\x10\x11\x12", $blobColPhpType); $this->assertSame('2023-07-11 14:50:23', $timePhpType); From 4c021b8985c070ece145f74d45a5b62921fd2fd6 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 12:35:53 +0700 Subject: [PATCH 14/16] Remove strtolower($dbType) MySQL always returns type in lower case --- src/Schema.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 0e51ac7aa..119385a8f 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -32,7 +32,6 @@ use function preg_match; use function serialize; use function stripos; -use function strtolower; use function trim; /** @@ -488,7 +487,7 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface $column->type(self::TYPE_STRING); if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $dbType, $matches)) { - $type = strtolower($matches[1]); + $type = $matches[1]; if (isset($this->typeMap[$type])) { $column->type($this->typeMap[$type]); @@ -571,7 +570,7 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf !empty($column->getExtra()) && !empty($defaultValue) => new Expression($defaultValue), - str_starts_with(strtolower((string) $column->getDbType()), 'bit') + str_starts_with((string) $column->getDbType(), 'bit') => $column->phpTypecast(bindec(trim($defaultValue, "b'"))), default => $column->phpTypecast($defaultValue), From e7fb2a69c9776b941c2df916fd338a2a9192d944 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 16:44:42 +0700 Subject: [PATCH 15/16] Revert strtolower($dbType) --- src/Schema.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 119385a8f..0e51ac7aa 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -32,6 +32,7 @@ use function preg_match; use function serialize; use function stripos; +use function strtolower; use function trim; /** @@ -487,7 +488,7 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface $column->type(self::TYPE_STRING); if (preg_match('/^(\w+)(?:\(([^)]+)\))?/', $dbType, $matches)) { - $type = $matches[1]; + $type = strtolower($matches[1]); if (isset($this->typeMap[$type])) { $column->type($this->typeMap[$type]); @@ -570,7 +571,7 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf !empty($column->getExtra()) && !empty($defaultValue) => new Expression($defaultValue), - str_starts_with((string) $column->getDbType(), 'bit') + str_starts_with(strtolower((string) $column->getDbType()), 'bit') => $column->phpTypecast(bindec(trim($defaultValue, "b'"))), default => $column->phpTypecast($defaultValue), From afa157bf01f94117969c1df487892b52d954e0b0 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 22:13:27 +0700 Subject: [PATCH 16/16] Split `match (true)` in `if` --- src/Schema.php | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 0e51ac7aa..15df52a8c 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -560,22 +560,30 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface */ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed { - return match (true) { - $defaultValue === null => null, + if ($defaultValue === null) { + return null; + } - $column->isPrimaryKey() => $column->phpTypecast($defaultValue), + if ($column->isPrimaryKey()) { + return $column->phpTypecast($defaultValue); + } + if ( in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME], true) && preg_match('/^current_timestamp(?:\((\d*)\))?$/i', $defaultValue, $matches) === 1 - => new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')), + ) { + return new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1]) ? '(' . $matches[1] . ')' : '')); + } - !empty($column->getExtra()) && !empty($defaultValue) => new Expression($defaultValue), + if (!empty($column->getExtra()) && !empty($defaultValue)) { + return new Expression($defaultValue); + } - str_starts_with(strtolower((string) $column->getDbType()), 'bit') - => $column->phpTypecast(bindec(trim($defaultValue, "b'"))), + if (str_starts_with(strtolower((string) $column->getDbType()), 'bit')) { + return $column->phpTypecast(bindec(trim($defaultValue, "b'"))); + } - default => $column->phpTypecast($defaultValue), - }; + return $column->phpTypecast($defaultValue); } /**