diff --git a/CHANGELOG.md b/CHANGELOG.md index 938e3960b..7f5a04842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ - Enh #865: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov, @vjik) - Enh #798: Allow `QueryInterface::one()` and `QueryInterface::all()` to return objects (@darkdef, @Tigrov) - Enh #872: Use `#[\SensitiveParameter]` attribute to mark sensitive parameters (@heap-s) -- New #864, #897, #898: Realize column factory (@Tigrov) +- New #864, #897, #898, #950: Realize column factory (@Tigrov) - Enh #875: Ignore "Packets out of order..." warnings in `AbstractPdoCommand::internalExecute()` method (@Tigrov) - Enh #877: Separate column type constants (@Tigrov) - New #878: Realize `ColumnBuilder` class (@Tigrov) diff --git a/src/Schema/Column/AbstractColumnFactory.php b/src/Schema/Column/AbstractColumnFactory.php index 7d0cbb8d9..59d837a43 100644 --- a/src/Schema/Column/AbstractColumnFactory.php +++ b/src/Schema/Column/AbstractColumnFactory.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Syntax\ColumnDefinitionParser; use function array_diff_key; +use function array_key_exists; use function array_merge; use function is_numeric; use function preg_match; @@ -121,7 +122,7 @@ public function fromType(string $type, array $info = []): ColumnInterface $column = new $columnClass($type, ...$info); - if (isset($info['defaultValueRaw'])) { + if (array_key_exists('defaultValueRaw', $info)) { $column->defaultValue($this->normalizeDefaultValue($info['defaultValueRaw'], $column)); } diff --git a/tests/Common/CommonSchemaTest.php b/tests/Common/CommonSchemaTest.php index c3e0235b6..96ca6a983 100644 --- a/tests/Common/CommonSchemaTest.php +++ b/tests/Common/CommonSchemaTest.php @@ -20,21 +20,21 @@ use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Schema\Column\ColumnBuilder; +use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\TableSchemaInterface; use Yiisoft\Db\Tests\AbstractSchemaTest; use Yiisoft\Db\Tests\Support\AnyCaseValue; use Yiisoft\Db\Tests\Support\AnyValue; +use Yiisoft\Db\Tests\Support\Assert; use Yiisoft\Db\Tests\Support\DbHelper; use function array_keys; use function count; use function gettype; use function is_array; -use function is_object; use function json_encode; use function ksort; use function mb_chr; -use function sort; use function str_replace; use function strtolower; @@ -833,122 +833,31 @@ protected function assertMetadataEquals($expected, $actual): void $this->assertEquals($expected, $actual); } - protected function assertTableColumns(array $columns, string $table): void + /** + * @param ColumnInterface[] $columns + */ + protected function assertTableColumns(array $columns, string $tableName): void { $db = $this->getConnection(true); - $table = $db->getTableSchema($table, true); + $table = $db->getTableSchema($tableName, true); $this->assertNotNull($table); - $expectedColNames = array_keys($columns); - sort($expectedColNames); - $colNames = $table->getColumnNames(); - sort($colNames); - - $this->assertSame($expectedColNames, $colNames); - - foreach ($table->getColumns() as $name => $column) { - $expected = $columns[$name]; - - $this->assertSame( - $expected['dbType'], - $column->getDbType(), - "dbType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}." - ); - $this->assertSame( - $expected['phpType'], - $column->getPhpType(), - "phpType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}." - ); - $this->assertSame( - $expected['primaryKey'], - $column->isPrimaryKey(), - "primaryKey of column $name does not match." - ); - $this->assertSame($expected['type'], $column->getType(), "type of column $name does not match."); - $this->assertSame( - $expected['notNull'], - $column->isNotNull(), - "notNull of column $name does not match." - ); - $this->assertSame( - $expected['autoIncrement'], - $column->isAutoIncrement(), - "autoIncrement of column $name does not match." - ); - $this->assertSame( - $expected['enumValues'], - $column->getEnumValues(), - "enumValues of column $name does not match." - ); - $this->assertSame($expected['size'], $column->getSize(), "size of column $name does not match."); - - $this->assertSame($expected['scale'], $column->getScale(), "scale of column $name does not match."); - - if (is_object($expected['defaultValue'])) { - $this->assertIsObject( - $column->getDefaultValue(), - "defaultValue of column $name is expected to be an object but it is not." - ); - $this->assertSame( - (string) $expected['defaultValue'], - (string) $column->getDefaultValue(), - "defaultValue of column $name does not match." - ); - } else { - $this->assertSame( - $expected['defaultValue'], - $column->getDefaultValue(), - "defaultValue of column $name does not match." - ); - } - - if (isset($expected['unique'])) { - $this->assertSame( - $expected['unique'], - $column->isUnique(), - "unique of column $name does not match" - ); - } - - if (isset($expected['unsigned'])) { - $this->assertSame( - $expected['unsigned'], - $column->isUnsigned(), - "unsigned of column $name does not match" - ); - } + foreach ($columns as $name => &$column) { + $column = $column->withName($name); - /* For array types */ - if (isset($expected['dimension'])) { - /** @psalm-suppress UndefinedMethod */ - $this->assertSame( - $expected['dimension'], - $column->getDimension(), - "dimension of column $name does not match" - ); + if ($column->isNotNull() === null) { + $column->notNull(false); } - if (isset($expected['column'])) { - /** @psalm-suppress UndefinedMethod */ - $arrayColumn = $column->getColumn(); - - $this->assertSame( - $expected['column'], - [ - 'type' => $arrayColumn->getType(), - 'dbType' => $arrayColumn->getDbType(), - 'phpType' => $arrayColumn->getPhpType(), - 'enumValues' => $arrayColumn->getEnumValues(), - 'size' => $arrayColumn->getSize(), - 'scale' => $arrayColumn->getScale(), - ], - "array column of column $name does not match" - ); + if ($column->getDefaultValue() === null) { + $column->defaultValue(null); } } + Assert::arraysEquals($columns, $table->getColumns(), "Columns of table '$tableName'."); + $db->close(); } diff --git a/tests/Support/Assert.php b/tests/Support/Assert.php index 92d398f72..8cc2abdf1 100644 --- a/tests/Support/Assert.php +++ b/tests/Support/Assert.php @@ -8,6 +8,11 @@ use ReflectionClass; use ReflectionException; use ReflectionObject; +use ReflectionProperty; + +use function is_array; +use function is_object; +use function ltrim; /** * @psalm-suppress PropertyNotSetInConstructor @@ -97,4 +102,79 @@ public static function invokeMethod(object $object, string $method, array $args return $method->invokeArgs($object, $args); } + + public static function arraysEquals(array $expected, mixed $actual, string $message = ''): void + { + self::assertIsArray($actual, $message); + self::assertCount(count($expected), $actual, $message); + + foreach ($expected as $key => $value) { + self::assertArrayHasKey($key, $actual, $message); + + $assertionMessage = ltrim($message . " Item by key '$key'."); + + if (is_object($value)) { + self::objectsEquals($value, $actual[$key], $assertionMessage); + } elseif (is_array($value)) { + self::arraysEquals($value, $actual[$key], $assertionMessage); + } else { + self::assertSame($value, $actual[$key], $assertionMessage); + } + } + } + + public static function objectsEquals(object $expected, mixed $actual, string $message = ''): void + { + self::assertIsObject($actual, $message); + + self::assertSame( + $expected::class, + $actual::class, + 'Expected ' . $expected::class . ' class name but ' . $actual::class . " provided. $message", + ); + + $properties = self::getProperties($expected); + + foreach ($properties as $property) { + $assertionMessage = ltrim("$message " . $expected::class . "::{$property->getName()} property."); + + if (!$property->isInitialized($expected)) { + self::assertFalse($property->isInitialized($actual), "$assertionMessage Property should not be initialized."); + + continue; + } + + self::assertTrue($property->isInitialized($actual), "$assertionMessage Property is not initialized."); + + $expectedValue = $property->getValue($expected); + $actualValue = $property->getValue($actual); + + if (is_object($expectedValue)) { + self::objectsEquals($expectedValue, $actualValue, $assertionMessage); + } elseif (is_array($expectedValue)) { + self::arraysEquals($expectedValue, $actualValue, $assertionMessage); + } else { + self::assertSame($expectedValue, $actualValue, $assertionMessage); + } + } + } + + /** + * Returns all properties of the object, including inherited ones. + * + * @return ReflectionProperty[] + */ + private static function getProperties(object $object): array + { + $properties = []; + $reflectionClass = new ReflectionClass($object); + + do { + foreach ($reflectionClass->getProperties() as $property) { + $properties[$property->getName()] ??= $property; + } + } while ($reflectionClass = $reflectionClass->getParentClass()); + + return $properties; + } }