diff --git a/src/Schema/AbstractSchema.php b/src/Schema/AbstractSchema.php index 0d3532b2e..78b42cc95 100644 --- a/src/Schema/AbstractSchema.php +++ b/src/Schema/AbstractSchema.php @@ -13,15 +13,6 @@ use Yiisoft\Db\Constraint\IndexConstraint; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Constant\GettypeResult; -use Yiisoft\Db\Schema\Column\BinaryColumnSchema; -use Yiisoft\Db\Schema\Column\BitColumnSchema; -use Yiisoft\Db\Schema\Column\BooleanColumnSchema; -use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; -use Yiisoft\Db\Schema\Column\DoubleColumnSchema; -use Yiisoft\Db\Schema\Column\IntegerColumnSchema; -use Yiisoft\Db\Schema\Column\JsonColumnSchema; -use Yiisoft\Db\Schema\Column\StringColumnSchema; -use Yiisoft\Db\Schema\Column\BigIntColumnSchema; use function gettype; use function is_array; @@ -374,49 +365,6 @@ protected function findTableNames(string $schema): array throw new NotSupportedException(static::class . ' does not support fetching all table names.'); } - /** - * Creates a column schema for the database. - * - * This method may be overridden by child classes to create a DBMS-specific column schema. - * - * @param string $type The abstract data type. - * @param mixed ...$info The column information. - * @psalm-param array{unsigned?: bool} $info The set of parameters may be different for a specific DBMS. - * - * @return ColumnSchemaInterface - */ - protected function createColumnSchema(string $type, mixed ...$info): ColumnSchemaInterface - { - $isUnsigned = !empty($info['unsigned']); - - $column = $this->createColumnSchemaFromType($type, $isUnsigned); - $column->unsigned($isUnsigned); - - return $column; - } - - protected function createColumnSchemaFromType(string $type, bool $isUnsigned = false): ColumnSchemaInterface - { - return match ($type) { - SchemaInterface::TYPE_BOOLEAN => new BooleanColumnSchema($type), - SchemaInterface::TYPE_BIT => new BitColumnSchema($type), - SchemaInterface::TYPE_TINYINT => new IntegerColumnSchema($type), - SchemaInterface::TYPE_SMALLINT => new IntegerColumnSchema($type), - SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE !== 8 && $isUnsigned - ? new BigIntColumnSchema($type) - : new IntegerColumnSchema($type), - SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE !== 8 || $isUnsigned - ? new BigIntColumnSchema($type) - : new IntegerColumnSchema($type), - SchemaInterface::TYPE_DECIMAL => new DoubleColumnSchema($type), - SchemaInterface::TYPE_FLOAT => new DoubleColumnSchema($type), - SchemaInterface::TYPE_DOUBLE => new DoubleColumnSchema($type), - SchemaInterface::TYPE_BINARY => new BinaryColumnSchema($type), - SchemaInterface::TYPE_JSON => new JsonColumnSchema($type), - default => new StringColumnSchema($type), - }; - } - /** * Returns the metadata of the given type for all tables in the given schema. * diff --git a/src/Schema/Column/AbstractColumnSchema.php b/src/Schema/Column/AbstractColumnSchema.php index f24f15427..2ae050bb9 100644 --- a/src/Schema/Column/AbstractColumnSchema.php +++ b/src/Schema/Column/AbstractColumnSchema.php @@ -6,6 +6,8 @@ use Yiisoft\Db\Constant\PhpType; +use function is_array; + /** * Represents the metadata of a column in a database table. * @@ -21,11 +23,10 @@ * ```php * use Yiisoft\Db\Schema\ColumnSchema; * - * $column = (new ColumnSchema()) + * $column = (new IntegerColumnSchema()) * ->name('id') * ->allowNull(false) * ->dbType('int(11)') - * ->phpType('integer') * ->type('integer') * ->defaultValue(0) * ->autoIncrement() @@ -182,6 +183,35 @@ public function isUnsigned(): bool return $this->unsigned; } + public function load(array $info): static + { + foreach ($info as $key => $value) { + /** + * @psalm-suppress PossiblyInvalidCast + * @psalm-suppress RiskyCast + */ + match ($key) { + 'allow_null' => $this->allowNull((bool) $value), + 'auto_increment' => $this->autoIncrement((bool) $value), + 'comment' => $this->comment($value !== null ? (string) $value : null), + 'computed' => $this->computed((bool) $value), + 'db_type' => $this->dbType($value !== null ? (string) $value : null), + 'default_value' => $this->defaultValue($value), + 'enum_values' => $this->enumValues(is_array($value) ? $value : null), + 'extra' => $this->extra($value !== null ? (string) $value : null), + 'name' => $this->name($value !== null ? (string) $value : null), + 'primary_key' => $this->primaryKey((bool) $value), + 'precision' => $this->precision($value !== null ? (int) $value : null), + 'scale' => $this->scale($value !== null ? (int) $value : null), + 'size' => $this->size($value !== null ? (int) $value : null), + 'unsigned' => $this->unsigned((bool) $value), + default => null, + }; + } + + return $this; + } + public function name(string|null $name): static { $this->name = $name; diff --git a/src/Schema/Column/ColumnFactory.php b/src/Schema/Column/ColumnFactory.php new file mode 100644 index 000000000..fa8ea3d3a --- /dev/null +++ b/src/Schema/Column/ColumnFactory.php @@ -0,0 +1,135 @@ +getType($dbType, $info); + + return $this->fromType($type, $info); + } + + public function fromDefinition(string $definition, array $info = []): ColumnSchemaInterface + { + preg_match('/^(\w*)(?:\(([^)]+)\))?\s*/', $definition, $matches); + + $dbType = strtolower($matches[1]); + + if (isset($matches[2])) { + $values = explode(',', $matches[2]); + $info['size'] = (int) $values[0]; + $info['precision'] = (int) $values[0]; + + if (isset($values[1])) { + $info['scale'] = (int) $values[1]; + } + } + + $extra = substr($definition, strlen($matches[0])); + + if (!empty($extra)) { + if (stripos($extra, 'unsigned') !== false) { + $info['unsigned'] = true; + $extra = trim(str_ireplace('unsigned', '', $extra)); + } + + if (!empty($extra)) { + if (empty($info['extra'])) { + $info['extra'] = $extra; + } else { + $info['extra'] = $extra . ' ' . $info['extra']; + } + } + } + + return $this->fromDbType($dbType, $info); + } + + public function fromType(string $type, array $info = []): ColumnSchemaInterface + { + $column = match ($type) { + SchemaInterface::TYPE_BOOLEAN => new BooleanColumnSchema($type), + SchemaInterface::TYPE_BIT => new BitColumnSchema($type), + SchemaInterface::TYPE_TINYINT => new IntegerColumnSchema($type), + SchemaInterface::TYPE_SMALLINT => new IntegerColumnSchema($type), + SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE !== 8 && !empty($info['unsigned']) + ? new BigIntColumnSchema($type) + : new IntegerColumnSchema($type), + SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE !== 8 || !empty($info['unsigned']) + ? new BigIntColumnSchema($type) + : new IntegerColumnSchema($type), + SchemaInterface::TYPE_DECIMAL => new DoubleColumnSchema($type), + SchemaInterface::TYPE_FLOAT => new DoubleColumnSchema($type), + SchemaInterface::TYPE_DOUBLE => new DoubleColumnSchema($type), + SchemaInterface::TYPE_BINARY => new BinaryColumnSchema($type), + SchemaInterface::TYPE_JSON => new JsonColumnSchema($type), + default => new StringColumnSchema($type), + }; + + return $column->load($info); + } + + /** + * Get the abstract database type for a database column type. + * + * @param string $dbType The database column type. + * @param array $info The column information. + * + * @return string The abstract database type. + * + * @psalm-param ColumnInfo $info + */ + protected function getType(string $dbType, array $info = []): string + { + return $this->isType($dbType) ? $dbType : SchemaInterface::TYPE_STRING; + } + + protected function isType(string $dbType): bool + { + return match ($dbType) { + SchemaInterface::TYPE_UUID, + SchemaInterface::TYPE_CHAR, + SchemaInterface::TYPE_STRING, + SchemaInterface::TYPE_TEXT, + SchemaInterface::TYPE_BINARY, + SchemaInterface::TYPE_BOOLEAN, + SchemaInterface::TYPE_TINYINT, + SchemaInterface::TYPE_SMALLINT, + SchemaInterface::TYPE_INTEGER, + SchemaInterface::TYPE_BIGINT, + SchemaInterface::TYPE_FLOAT, + SchemaInterface::TYPE_DOUBLE, + SchemaInterface::TYPE_DECIMAL, + SchemaInterface::TYPE_MONEY, + SchemaInterface::TYPE_DATETIME, + SchemaInterface::TYPE_TIMESTAMP, + SchemaInterface::TYPE_TIME, + SchemaInterface::TYPE_DATE, + SchemaInterface::TYPE_JSON => true, + default => false, + }; + } +} diff --git a/src/Schema/Column/ColumnFactoryInterface.php b/src/Schema/Column/ColumnFactoryInterface.php new file mode 100644 index 000000000..6f4656d5b --- /dev/null +++ b/src/Schema/Column/ColumnFactoryInterface.php @@ -0,0 +1,44 @@ +getConnection(); + $factory = $db->getSchema()->getColumnFactory(); + + $column = $factory->fromDbType($dbType); + + $this->assertInstanceOf($expectedInstanceOf, $column); + $this->assertSame($expectedType, $column->getType()); + } + + /** + * @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::definitions + * @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::types + */ + public function testFromDefinition( + string $definition, + string $expectedType, + string $expectedInstanceOf, + array $expectedInfo = [] + ): void { + $db = $this->getConnection(); + $factory = $db->getSchema()->getColumnFactory(); + + $column = $factory->fromDefinition($definition); + + $this->assertInstanceOf($expectedInstanceOf, $column); + $this->assertSame($expectedType, $column->getType()); + + foreach ($expectedInfo as $method => $value) { + $this->assertSame($value, $column->$method()); + } + } + + /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnFactoryProvider::types */ + public function testFromType(string $type, string $expectedType, string $expectedInstanceOf): void + { + $db = $this->getConnection(); + $factory = $db->getSchema()->getColumnFactory(); + + $column = $factory->fromType($type); + + $this->assertInstanceOf($expectedInstanceOf, $column); + $this->assertSame($expectedType, $column->getType()); + } +} diff --git a/tests/Db/Schema/Column/ColumnFactoryTest.php b/tests/Db/Schema/Column/ColumnFactoryTest.php new file mode 100644 index 000000000..b03aae921 --- /dev/null +++ b/tests/Db/Schema/Column/ColumnFactoryTest.php @@ -0,0 +1,14 @@ +assertSame('', $column->getExtra()); } + /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnSchemaProvider::load */ + public function testLoad(string $parameter, mixed $value, string $method, mixed $expected): void + { + $column = new ColumnSchema(); + + $column->load([$parameter => $value]); + + $this->assertSame($expected, $column->$method()); + } + public function testName(): void { $column = new ColumnSchema(); diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php new file mode 100644 index 000000000..60062d881 --- /dev/null +++ b/tests/Provider/ColumnFactoryProvider.php @@ -0,0 +1,53 @@ + ['', 'string', StringColumnSchema::class], + 'text' => ['text', 'text', StringColumnSchema::class], + 'text NOT NULL' => ['text NOT NULL', 'text', StringColumnSchema::class, ['getExtra' => 'NOT NULL']], + 'char(1)' => ['char(1)', 'char', StringColumnSchema::class, ['getSize' => 1]], + 'decimal(10,2)' => ['decimal(10,2)', 'decimal', DoubleColumnSchema::class, ['getPrecision' => 10, 'getScale' => 2]], + 'bigint UNSIGNED' => ['bigint UNSIGNED', 'bigint', BigIntColumnSchema::class, ['isUnsigned' => true]], + ]; + } + + public static function types(): array + { + return [ + 'uuid' => ['uuid', 'uuid', StringColumnSchema::class], + 'char' => ['char', 'char', StringColumnSchema::class], + 'string' => ['string', 'string', StringColumnSchema::class], + 'text' => ['text', 'text', StringColumnSchema::class], + 'binary' => ['binary', 'binary', BinaryColumnSchema::class], + 'boolean' => ['boolean', 'boolean', BooleanColumnSchema::class], + 'tinyint' => ['tinyint', 'tinyint', IntegerColumnSchema::class], + 'smallint' => ['smallint', 'smallint', IntegerColumnSchema::class], + 'integer' => ['integer', 'integer', IntegerColumnSchema::class], + 'bigint' => ['bigint', 'bigint', IntegerColumnSchema::class], + 'float' => ['float', 'float', DoubleColumnSchema::class], + 'double' => ['double', 'double', DoubleColumnSchema::class], + 'decimal' => ['decimal', 'decimal', DoubleColumnSchema::class], + 'money' => ['money', 'money', StringColumnSchema::class], + 'datetime' => ['datetime', 'datetime', StringColumnSchema::class], + 'timestamp' => ['timestamp', 'timestamp', StringColumnSchema::class], + 'time' => ['time', 'time', StringColumnSchema::class], + 'date' => ['date', 'date', StringColumnSchema::class], + 'json' => ['json', 'json', JsonColumnSchema::class], + ]; + } +} diff --git a/tests/Provider/ColumnSchemaProvider.php b/tests/Provider/ColumnSchemaProvider.php index d5c070476..cce582793 100644 --- a/tests/Provider/ColumnSchemaProvider.php +++ b/tests/Provider/ColumnSchemaProvider.php @@ -243,4 +243,45 @@ public static function phpTypecastColumns(): array ], ]; } + + public static function load(): array + { + return [ + // parameter, value, method to get value, expected value + ['allow_null', true, 'isAllowNull', true], + ['allow_null', false, 'isAllowNull', false], + ['allow_null', '1', 'isAllowNull', true], + ['allow_null', '0', 'isAllowNull', false], + ['auto_increment', true, 'isAutoIncrement', true], + ['auto_increment', false, 'isAutoIncrement', false], + ['auto_increment', '1', 'isAutoIncrement', true], + ['auto_increment', '0', 'isAutoIncrement', false], + ['comment', 'Lorem ipsum', 'getComment', 'Lorem ipsum'], + ['comment', null, 'getComment', null], + ['computed', true, 'isComputed', true], + ['computed', false, 'isComputed', false], + ['computed', '1', 'isComputed', true], + ['computed', '0', 'isComputed', false], + ['db_type', 'integer', 'getDbType', 'integer'], + ['db_type', null, 'getDbType', null], + ['default_value', 'default_value', 'getDefaultValue', 'default_value'], + ['default_value', null, 'getDefaultValue', null], + ['enum_values', ['value1', 'value2'], 'getEnumValues', ['value1', 'value2']], + ['enum_values', null, 'getEnumValues', null], + ['extra', 'CHARACTER SET utf8mb4', 'getExtra', 'CHARACTER SET utf8mb4'], + ['extra', null, 'getExtra', null], + ['name', 'name', 'getName', 'name'], + ['name', null, 'getName', null], + ['precision', 10, 'getPrecision', 10], + ['precision', null, 'getPrecision', null], + ['scale', 2, 'getScale', 2], + ['scale', null, 'getScale', null], + ['size', 255, 'getSize', 255], + ['size', null, 'getSize', null], + ['unsigned', true, 'isUnsigned', true], + ['unsigned', false, 'isUnsigned', false], + ['unsigned', '1', 'isUnsigned', true], + ['unsigned', '0', 'isUnsigned', false], + ]; + } } diff --git a/tests/Support/Stub/Schema.php b/tests/Support/Stub/Schema.php index 554299ccb..27e7f80ff 100644 --- a/tests/Support/Stub/Schema.php +++ b/tests/Support/Stub/Schema.php @@ -8,6 +8,8 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Schema\AbstractSchema; use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnFactory; +use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\TableSchemaInterface; /** @@ -22,6 +24,11 @@ public function createColumn(string $type, array|int|string $length = null): Col return new Column($type, $length); } + public function getColumnFactory(): ColumnFactoryInterface + { + return new ColumnFactory(); + } + public function findUniqueIndexes(TableSchemaInterface $table): array { throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.');