diff --git a/src/Generator/GenerateTypecast.php b/src/Generator/GenerateTypecast.php index b532048..ab35e94 100644 --- a/src/Generator/GenerateTypecast.php +++ b/src/Generator/GenerateTypecast.php @@ -4,10 +4,10 @@ namespace Cycle\Schema\Generator; +use Cycle\Database\Schema\AbstractColumn; use Cycle\Schema\Definition\Entity; use Cycle\Schema\GeneratorInterface; use Cycle\Schema\Registry; -use Cycle\Database\Schema\AbstractColumn; /** * Must be run after RenderTable. @@ -22,19 +22,66 @@ final class GenerateTypecast implements GeneratorInterface public function run(Registry $registry): Registry { foreach ($registry as $entity) { - $this->compute($registry, $entity); + $this->computeByClassPropertyType($entity); + $this->computeByFieldType($entity); + $this->computeByColumn($registry, $entity); } return $registry; } + private function computeByClassPropertyType(Entity $entity): void + { + $refClass = new \ReflectionClass($entity->getClass()); + foreach ($entity->getFields() as $field) { + if ($field->hasTypecast()) { + continue; + } + if (!$refClass->hasProperty($field->getColumn())) { + continue; + } + + $refProp = $refClass->getProperty($field->getColumn()); + if (!$refProp->hasType() || !$refProp->getType()->isBuiltin()) { + continue; + } + + $field->setTypecast( + match ($refProp->getType()->getName()) { + 'bool' => 'bool', + 'int' => 'int', + 'string' => 'string', + default => null + } + ); + } + } + + private function computeByFieldType(Entity $entity): void + { + foreach ($entity->getFields() as $field) { + if ($field->hasTypecast()) { + continue; + } + + $field->setTypecast( + match ($field->getType()) { + 'bool', 'boolean' => 'bool', + 'int', 'integer' => 'int', + 'string' => 'string', + default => null + } + ); + } + } + /** * Automatically clarify column types based on table column types. * * @param Registry $registry * @param Entity $entity */ - protected function compute(Registry $registry, Entity $entity): void + protected function computeByColumn(Registry $registry, Entity $entity): void { if (!$registry->hasTable($entity)) { return; diff --git a/tests/Schema/Fixtures/EntityForTypecast.php b/tests/Schema/Fixtures/EntityForTypecast.php new file mode 100644 index 0000000..2b14e8a --- /dev/null +++ b/tests/Schema/Fixtures/EntityForTypecast.php @@ -0,0 +1,35 @@ +createMock(Registry::class); + $registry + ->expects(self::atLeastOnce()) + ->method('getFields') + ->willReturn($field); + $generator = new BoolTypecastGenerator(); + $generator->run($registry); + self::assertSame($expectedTypecast, $field->getTypecast()); + } + + public function dataSetTypecast(): iterable + { + yield [ + (new Field())->setType('boolean'), + 'bool', + ]; + + yield [ + (new Field())->setType('boolean')->setTypecast('foo'), + 'foo', + ]; + + yield [ + (new Field())->setType('bool'), + null, + ]; + + yield [ + (new Field())->setType('int'), + null, + ]; + } +} diff --git a/tests/Schema/Generator/TypecastGeneratorTest.php b/tests/Schema/Generator/TypecastGeneratorTest.php index 1c49bc4..2368ce9 100644 --- a/tests/Schema/Generator/TypecastGeneratorTest.php +++ b/tests/Schema/Generator/TypecastGeneratorTest.php @@ -6,6 +6,8 @@ use Cycle\ORM\Schema; use Cycle\Schema\Compiler; +use Cycle\Schema\Definition\Entity; +use Cycle\Schema\Definition\Field; use Cycle\Schema\Generator\GenerateTypecast; use Cycle\Schema\Generator\RenderTables; use Cycle\Schema\Registry; @@ -24,11 +26,104 @@ public function testCompiledUser(): void $c = new Compiler(); $schema = $c->compile($r, [new RenderTables(), new GenerateTypecast()]); - $this->assertSame('int', $schema['user'][Schema::TYPECAST]['p_id']); $this->assertSame('float', $schema['user'][Schema::TYPECAST]['p_balance']); $this->assertSame('datetime', $schema['user'][Schema::TYPECAST]['p_created_at']); $this->assertTrue(in_array($schema['user'][Schema::TYPECAST]['p_id'], ['int', 'bool'])); } + + /** + * @dataProvider dataTypecast + */ + public function testTypecast(Field $field, ?string $expectedTypecast): void + { + $entity = new Entity(); + $entity->setRole('entityForTypecast'); + $entity->setClass(\Cycle\Schema\Tests\Fixtures\EntityForTypecast::class); + $entity->getFields()->set($field->getColumn(), $field); + + $r = new Registry($this->dbal); + $r->register($entity)->linkTable($entity, 'default', 'entityForTypecast'); + + $c = new Compiler(); + $c->compile($r, [new RenderTables(), new GenerateTypecast()]); + self::assertSame($expectedTypecast, $field->getTypecast()); + } + + public function dataTypecast(): iterable + { + // based on property type + yield 'int_integer' => [ + (new Field())->setType('integer')->setColumn('int_integer'), + 'int', + ]; + + yield 'int_tinyInteger' => [ + (new Field())->setType('tinyInteger')->setColumn('int_tinyInteger'), + 'int', + ]; + + yield 'int_bigInteger' => [ + (new Field())->setType('bigInteger')->setColumn('int_bigInteger'), + 'int', + ]; + + yield 'bool_boolean' => [ + (new Field())->setType('boolean')->setColumn('bool_boolean'), + 'bool', + ]; + + yield 'string_string' => [ + (new Field())->setType('string')->setColumn('string_string'), + 'string', + ]; + + // based on orm type + yield '_integer' => [ + (new Field())->setType('integer')->setColumn('_integer'), + 'int', + ]; + + yield '_boolean' => [ + (new Field())->setType('boolean')->setColumn('_boolean'), + 'bool', + ]; + + yield '_string' => [ + (new Field())->setType('string')->setColumn('_string'), + 'string', + ]; + + // based on property type + yield 'int_boolean' => [ + (new Field())->setType('boolean')->setColumn('int_boolean'), + 'int', + ]; + + yield 'int_string' => [ + (new Field())->setType('string')->setColumn('int_string'), + 'int', + ]; + + yield 'bool_integer' => [ + (new Field())->setType('integer')->setColumn('bool_integer'), + 'bool', + ]; + + yield 'bool_string' => [ + (new Field())->setType('string')->setColumn('bool_string'), + 'bool', + ]; + + yield 'string_boolean' => [ + (new Field())->setType('boolean')->setColumn('string_boolean'), + 'string', + ]; + + yield 'string_integer' => [ + (new Field())->setType('integer')->setColumn('string_integer'), + 'string', + ]; + } }