From f9eacb0f3d5014127d80e070b08a6786e9f6cea5 Mon Sep 17 00:00:00 2001 From: Oleksii Date: Thu, 17 Mar 2022 03:32:56 +0200 Subject: [PATCH 1/4] feat: column type by ReflectionProperty --- src/Annotation/Column.php | 16 ++++-- src/Configurator.php | 54 ++++++++++++++----- .../Fixtures/Fixtures1/SomeEntity.php | 35 ++++++++++++ .../Driver/Common/GeneratorTest.php | 29 ++++++++++ .../Functional/Driver/Common/InvalidTest.php | 2 +- 5 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 tests/Annotated/Fixtures/Fixtures1/SomeEntity.php diff --git a/src/Annotation/Column.php b/src/Annotation/Column.php index 7e534c20..348c6383 100644 --- a/src/Annotation/Column.php +++ b/src/Annotation/Column.php @@ -21,6 +21,7 @@ final class Column { private bool $hasDefault = false; + private bool $hasNullable = false; /** * @param non-empty-string $type Column type. {@see \Cycle\Database\Schema\AbstractColumn::$mapping} @@ -41,11 +42,11 @@ public function __construct( 'string', 'text', 'tinyText', 'longText', 'double', 'float', 'decimal', 'datetime', 'date', 'time', 'timestamp', 'binary', 'tinyBinary', 'longBinary', 'json', ])] - private string $type, + private ?string $type = null, private ?string $name = null, private ?string $property = null, private bool $primary = false, - private bool $nullable = false, + private ?bool $nullable = null, private mixed $default = null, private mixed $typecast = null, private bool $castDefault = false, @@ -53,6 +54,10 @@ public function __construct( if ($default !== null) { $this->hasDefault = true; } + + if ($this->nullable !== null) { + $this->hasNullable = true; + } } public function getType(): ?string @@ -72,7 +77,7 @@ public function getProperty(): ?string public function isNullable(): bool { - return $this->nullable; + return $this->nullable === true; } public function isPrimary(): bool @@ -85,6 +90,11 @@ public function hasDefault(): bool return $this->hasDefault; } + public function hasNullable(): bool + { + return $this->hasNullable; + } + public function getDefault(): mixed { return $this->default; diff --git a/src/Configurator.php b/src/Configurator.php index c90fc76e..d5ca4a8f 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -22,6 +22,7 @@ use Doctrine\Inflector\Rules\English\InflectorFactory; use Exception; use Spiral\Attributes\ReaderInterface; +use function is_subclass_of; final class Configurator { @@ -101,7 +102,7 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string continue; } - $field = $this->initField($property->getName(), $column, $class, $columnPrefix); + $field = $this->initField($property, $column, $class, $columnPrefix); $field->setEntityClass($property->getDeclaringClass()->getName()); $entity->getFields()->set($property->getName(), $field); } @@ -208,35 +209,64 @@ public function initColumns(EntitySchema $entity, array $columns, \ReflectionCla ); } - if ($column->getType() === null) { - throw new AnnotationException( - "Column type definition is required on `{$entity->getClass()}`.`{$columnName}`" - ); - } - $field = $this->initField($columnName, $column, $class, ''); $field->setEntityClass($entity->getClass()); $entity->getFields()->set($propertyName, $field); } } - public function initField(string $name, Column $column, \ReflectionClass $class, string $columnPrefix): Field + public function initField(string|\ReflectionProperty $nameOrProperty, Column $column, \ReflectionClass $class, string $columnPrefix): Field { + $type = $column->getType(); + $isNullable = $column->hasNullable() ? $column->isNullable() : null; + $hasDefault = $column->hasDefault(); + $default = $column->getDefault(); + + if ($nameOrProperty instanceof \ReflectionProperty) { + $name = ($property = $nameOrProperty)->getName(); + $propertyType = $property->getType(); + + if ($property->hasDefaultValue() && !$hasDefault) { + $hasDefault = true; + $default = $property->getDefaultValue(); + } + + if ($propertyType instanceof \ReflectionType) { + $isNullable ??= $propertyType->allowsNull(); + + if ($propertyType instanceof \ReflectionNamedType) { + if ($propertyType->isBuiltin()) { + $type ??= $propertyType->getName(); + } elseif (is_subclass_of($propertyType->getName(), \DateTimeInterface::class)) { + $type = "datetime"; + } + } + } + } else { + $name = $nameOrProperty; + } + + if ($type === null) { + throw new AnnotationException( + "Column type definition is required on `{$class->getName()}`.`{$name}`" + ); + } + $field = new Field(); - $field->setType($column->getType()); + $field->setType($type); $field->setColumn($columnPrefix . ($column->getColumn() ?? $this->inflector->tableize($name))); $field->setPrimary($column->isPrimary()); $field->setTypecast($this->resolveTypecast($column->getTypecast(), $class)); - if ($column->isNullable()) { + if ($isNullable) { $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_NULLABLE, true); $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_DEFAULT, null); } - if ($column->hasDefault()) { - $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_DEFAULT, $column->getDefault()); + if ($hasDefault) { + $field->getOptions()->set(\Cycle\Schema\Table\Column::OPT_DEFAULT, $default); } if ($column->castDefault()) { diff --git a/tests/Annotated/Fixtures/Fixtures1/SomeEntity.php b/tests/Annotated/Fixtures/Fixtures1/SomeEntity.php new file mode 100644 index 00000000..f22c3047 --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures1/SomeEntity.php @@ -0,0 +1,35 @@ +assertFalse($r->getEntity('eComplete')->getFields()->has('ignored')); } + + /** + * @dataProvider allReadersProvider + */ + public function testSimpleReferredSchema(ReaderInterface $reader): void + { + $r = new Registry($this->dbal); + (new Entities($this->locator, $reader))->run($r); + + $fields = $r->getEntity(SomeEntity::class)->getFields(); + + $this->assertSame("int", $fields->get('idificator')->getType()); + $this->assertFalse($fields->get('idificator')->getOptions()->has(Column::OPT_NULLABLE)); + $this->assertFalse($fields->get('idificator')->getOptions()->has(Column::OPT_DEFAULT)); + + $this->assertSame("string", $fields->get('nullableString')->getType()); + $this->assertSame(true, $fields->get('nullableString')->getOptions()->get(Column::OPT_NULLABLE)); + $this->assertSame(null, $fields->get('nullableString')->getOptions()->get(Column::OPT_DEFAULT)); + + $this->assertSame("string", $fields->get('nullableStringWithDefault')->getType()); + $this->assertSame(true, $fields->get('nullableStringWithDefault')->getOptions()->get(Column::OPT_NULLABLE)); + $this->assertSame("123", $fields->get('nullableStringWithDefault')->getOptions()->get(Column::OPT_DEFAULT)); + + $this->assertSame("datetime", $fields->get('dateTime')->getType()); + + $this->assertFalse($fields->get('columnDeclaredInClass')->getOptions()->has(Column::OPT_DEFAULT)); + } } diff --git a/tests/Annotated/Functional/Driver/Common/InvalidTest.php b/tests/Annotated/Functional/Driver/Common/InvalidTest.php index 8a83f476..134b5fda 100644 --- a/tests/Annotated/Functional/Driver/Common/InvalidTest.php +++ b/tests/Annotated/Functional/Driver/Common/InvalidTest.php @@ -61,7 +61,7 @@ public function testNotDefinedColumnTypeShouldThrowAnException(ReaderInterface $ { $this->expectException(AnnotationException::class); $this->expectErrorMessage( - 'Some of required arguments [`type`] is missed on `Cycle\Annotated\Tests\Fixtures\Fixtures4\User.id.`' + 'Column type definition is required on `Cycle\Annotated\Tests\Fixtures\Fixtures4\User`.`id`' ); $tokenizer = new Tokenizer(new TokenizerConfig([ From f42be818773381ef959b063bb1160ee787d2136b Mon Sep 17 00:00:00 2001 From: Oleksii Date: Thu, 17 Mar 2022 03:38:09 +0200 Subject: [PATCH 2/4] fix cs --- src/Configurator.php | 2 +- .../Functional/Driver/Common/GeneratorTest.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Configurator.php b/src/Configurator.php index d5ca4a8f..cb4a7a10 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -238,7 +238,7 @@ public function initField(string|\ReflectionProperty $nameOrProperty, Column $co if ($propertyType->isBuiltin()) { $type ??= $propertyType->getName(); } elseif (is_subclass_of($propertyType->getName(), \DateTimeInterface::class)) { - $type = "datetime"; + $type = 'datetime'; } } } diff --git a/tests/Annotated/Functional/Driver/Common/GeneratorTest.php b/tests/Annotated/Functional/Driver/Common/GeneratorTest.php index 9527cfc7..457e8f5c 100644 --- a/tests/Annotated/Functional/Driver/Common/GeneratorTest.php +++ b/tests/Annotated/Functional/Driver/Common/GeneratorTest.php @@ -164,19 +164,19 @@ public function testSimpleReferredSchema(ReaderInterface $reader): void $fields = $r->getEntity(SomeEntity::class)->getFields(); - $this->assertSame("int", $fields->get('idificator')->getType()); + $this->assertSame('int', $fields->get('idificator')->getType()); $this->assertFalse($fields->get('idificator')->getOptions()->has(Column::OPT_NULLABLE)); $this->assertFalse($fields->get('idificator')->getOptions()->has(Column::OPT_DEFAULT)); - $this->assertSame("string", $fields->get('nullableString')->getType()); + $this->assertSame('string', $fields->get('nullableString')->getType()); $this->assertSame(true, $fields->get('nullableString')->getOptions()->get(Column::OPT_NULLABLE)); $this->assertSame(null, $fields->get('nullableString')->getOptions()->get(Column::OPT_DEFAULT)); - $this->assertSame("string", $fields->get('nullableStringWithDefault')->getType()); + $this->assertSame('string', $fields->get('nullableStringWithDefault')->getType()); $this->assertSame(true, $fields->get('nullableStringWithDefault')->getOptions()->get(Column::OPT_NULLABLE)); - $this->assertSame("123", $fields->get('nullableStringWithDefault')->getOptions()->get(Column::OPT_DEFAULT)); + $this->assertSame('123', $fields->get('nullableStringWithDefault')->getOptions()->get(Column::OPT_DEFAULT)); - $this->assertSame("datetime", $fields->get('dateTime')->getType()); + $this->assertSame('datetime', $fields->get('dateTime')->getType()); $this->assertFalse($fields->get('columnDeclaredInClass')->getOptions()->has(Column::OPT_DEFAULT)); } From 9ec526e3e530007b555a71c533b06b9fd670f64a Mon Sep 17 00:00:00 2001 From: Oleksii Date: Thu, 17 Mar 2022 23:24:34 +0200 Subject: [PATCH 3/4] fix test on php8.0 --- .../Annotated/Fixtures/Fixtures1/SomeEntity.php | 6 ++---- .../Fixtures/Fixtures1/WithColumnInEntity.php | 17 +++++++++++++++++ .../Functional/Driver/Common/GeneratorTest.php | 11 ++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 tests/Annotated/Fixtures/Fixtures1/WithColumnInEntity.php diff --git a/tests/Annotated/Fixtures/Fixtures1/SomeEntity.php b/tests/Annotated/Fixtures/Fixtures1/SomeEntity.php index f22c3047..3c51a580 100644 --- a/tests/Annotated/Fixtures/Fixtures1/SomeEntity.php +++ b/tests/Annotated/Fixtures/Fixtures1/SomeEntity.php @@ -7,8 +7,8 @@ use Cycle\Annotated\Annotation\Column; use Cycle\Annotated\Annotation\Entity; -/** * @Entity(table="SomeEntity", columns={@Column(name="columnDeclaredInClass", type="integer")}) */ -#[Entity(table: "SomeEntity", columns: [new Column(name: "columnDeclaredInClass", type: 'integer')])] +/** * @Entity(table="SomeEntity") */ +#[Entity(table: "SomeEntity")] class SomeEntity implements LabelledInterface { /** @Column(type="primary") */ @@ -30,6 +30,4 @@ class SomeEntity implements LabelledInterface /** @Column */ #[Column] public \DateTimeImmutable $dateTime; - - public $columnDeclaredInClass = 123; } \ No newline at end of file diff --git a/tests/Annotated/Fixtures/Fixtures1/WithColumnInEntity.php b/tests/Annotated/Fixtures/Fixtures1/WithColumnInEntity.php new file mode 100644 index 00000000..11da45fb --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures1/WithColumnInEntity.php @@ -0,0 +1,17 @@ +assertSame('123', $fields->get('nullableStringWithDefault')->getOptions()->get(Column::OPT_DEFAULT)); $this->assertSame('datetime', $fields->get('dateTime')->getType()); + } + + public function testSimpleReferredSchemaWithColumnInEntity(): void{ + $reader = new AnnotationReader(); + $r = new Registry($this->dbal); + (new Entities($this->locator, $reader))->run($r); + + $fields = $r->getEntity(WithColumnInEntity::class)->getFields(); - $this->assertFalse($fields->get('columnDeclaredInClass')->getOptions()->has(Column::OPT_DEFAULT)); + $this->assertFalse($fields->get('columnDeclaredInEntity')->getOptions()->has(Column::OPT_DEFAULT)); } } From f630c7fe65cc6e8c453c7711ab928c8d978accbd Mon Sep 17 00:00:00 2001 From: Oleksii Date: Thu, 17 Mar 2022 23:32:23 +0200 Subject: [PATCH 4/4] fix cs --- tests/Annotated/Functional/Driver/Common/GeneratorTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Annotated/Functional/Driver/Common/GeneratorTest.php b/tests/Annotated/Functional/Driver/Common/GeneratorTest.php index 1f966573..ecb09fdb 100644 --- a/tests/Annotated/Functional/Driver/Common/GeneratorTest.php +++ b/tests/Annotated/Functional/Driver/Common/GeneratorTest.php @@ -180,7 +180,8 @@ public function testSimpleReferredSchema(ReaderInterface $reader): void $this->assertSame('datetime', $fields->get('dateTime')->getType()); } - public function testSimpleReferredSchemaWithColumnInEntity(): void{ + public function testSimpleReferredSchemaWithColumnInEntity(): void + { $reader = new AnnotationReader(); $r = new Registry($this->dbal); (new Entities($this->locator, $reader))->run($r);