diff --git a/composer.json b/composer.json index 3d6ddc7c..0e9caba6 100644 --- a/composer.json +++ b/composer.json @@ -7,8 +7,8 @@ "prefer-stable": true, "require": { "php": ">=8.0", - "cycle/orm": "^2.2.0", - "cycle/schema-builder": "^2.6", + "cycle/orm": "^2.7", + "cycle/schema-builder": "^2.8", "doctrine/annotations": "^1.14.3 || ^2.0.1", "spiral/attributes": "^2.8|^3.0", "spiral/tokenizer": "^2.8|^3.0", diff --git a/src/Annotation/Column.php b/src/Annotation/Column.php index b36ff7b0..3ac8d41b 100644 --- a/src/Annotation/Column.php +++ b/src/Annotation/Column.php @@ -11,9 +11,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target({"PROPERTY", "ANNOTATION", "CLASS"}) */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] diff --git a/src/Annotation/Embeddable.php b/src/Annotation/Embeddable.php index 3d5cd434..41889f1d 100644 --- a/src/Annotation/Embeddable.php +++ b/src/Annotation/Embeddable.php @@ -9,9 +9,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("CLASS") */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] diff --git a/src/Annotation/Entity.php b/src/Annotation/Entity.php index 140bd698..b1e60507 100644 --- a/src/Annotation/Entity.php +++ b/src/Annotation/Entity.php @@ -9,9 +9,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("CLASS") */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] diff --git a/src/Annotation/ForeignKey.php b/src/Annotation/ForeignKey.php index f77bea6d..37b593af 100644 --- a/src/Annotation/ForeignKey.php +++ b/src/Annotation/ForeignKey.php @@ -10,9 +10,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target({"PROPERTY", "ANNOTATION", "CLASS"}) */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] diff --git a/src/Annotation/GeneratedValue.php b/src/Annotation/GeneratedValue.php new file mode 100644 index 00000000..8c108a1f --- /dev/null +++ b/src/Annotation/GeneratedValue.php @@ -0,0 +1,37 @@ +beforeInsert && !$this->onInsert && !$this->beforeUpdate) { + return null; + } + + return + ($this->beforeInsert ? GeneratedField::BEFORE_INSERT : 0) | + ($this->onInsert ? GeneratedField::ON_INSERT : 0) | + ($this->beforeUpdate ? GeneratedField::BEFORE_UPDATE : 0); + } +} diff --git a/src/Annotation/Inheritance/DiscriminatorColumn.php b/src/Annotation/Inheritance/DiscriminatorColumn.php index cb118d3a..06298ab4 100644 --- a/src/Annotation/Inheritance/DiscriminatorColumn.php +++ b/src/Annotation/Inheritance/DiscriminatorColumn.php @@ -8,9 +8,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("CLASS") */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] diff --git a/src/Annotation/Inheritance/JoinedTable.php b/src/Annotation/Inheritance/JoinedTable.php index 591c7737..258c4642 100644 --- a/src/Annotation/Inheritance/JoinedTable.php +++ b/src/Annotation/Inheritance/JoinedTable.php @@ -10,9 +10,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("CLASS") */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] diff --git a/src/Annotation/Inheritance/SingleTable.php b/src/Annotation/Inheritance/SingleTable.php index a47e963b..3d38221b 100644 --- a/src/Annotation/Inheritance/SingleTable.php +++ b/src/Annotation/Inheritance/SingleTable.php @@ -9,9 +9,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("CLASS") */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/BelongsTo.php b/src/Annotation/Relation/BelongsTo.php index 19bce0c6..f6212b07 100644 --- a/src/Annotation/Relation/BelongsTo.php +++ b/src/Annotation/Relation/BelongsTo.php @@ -11,9 +11,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/Embedded.php b/src/Annotation/Relation/Embedded.php index ac08c549..bed3274b 100644 --- a/src/Annotation/Relation/Embedded.php +++ b/src/Annotation/Relation/Embedded.php @@ -9,9 +9,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/HasMany.php b/src/Annotation/Relation/HasMany.php index 15c5370c..c3d9aceb 100644 --- a/src/Annotation/Relation/HasMany.php +++ b/src/Annotation/Relation/HasMany.php @@ -10,9 +10,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/HasOne.php b/src/Annotation/Relation/HasOne.php index 4c00f69a..cad1c7db 100644 --- a/src/Annotation/Relation/HasOne.php +++ b/src/Annotation/Relation/HasOne.php @@ -11,9 +11,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/Inverse.php b/src/Annotation/Relation/Inverse.php index 9995b2e4..c8efed13 100644 --- a/src/Annotation/Relation/Inverse.php +++ b/src/Annotation/Relation/Inverse.php @@ -11,9 +11,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target({"PROPERTY", "ANNOTATION"}) */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/ManyToMany.php b/src/Annotation/Relation/ManyToMany.php index f9634ea7..8bb9e951 100644 --- a/src/Annotation/Relation/ManyToMany.php +++ b/src/Annotation/Relation/ManyToMany.php @@ -11,9 +11,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/Morphed/BelongsToMorphed.php b/src/Annotation/Relation/Morphed/BelongsToMorphed.php index 30f463cf..30c467be 100644 --- a/src/Annotation/Relation/Morphed/BelongsToMorphed.php +++ b/src/Annotation/Relation/Morphed/BelongsToMorphed.php @@ -13,9 +13,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/Morphed/MorphedHasMany.php b/src/Annotation/Relation/Morphed/MorphedHasMany.php index 7e1bd215..f78c427c 100644 --- a/src/Annotation/Relation/Morphed/MorphedHasMany.php +++ b/src/Annotation/Relation/Morphed/MorphedHasMany.php @@ -12,9 +12,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/Morphed/MorphedHasOne.php b/src/Annotation/Relation/Morphed/MorphedHasOne.php index da8078c1..64ea3bd5 100644 --- a/src/Annotation/Relation/Morphed/MorphedHasOne.php +++ b/src/Annotation/Relation/Morphed/MorphedHasOne.php @@ -12,9 +12,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Relation/RefersTo.php b/src/Annotation/Relation/RefersTo.php index 4b772b2a..6650e44a 100644 --- a/src/Annotation/Relation/RefersTo.php +++ b/src/Annotation/Relation/RefersTo.php @@ -11,9 +11,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("PROPERTY") */ #[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor] diff --git a/src/Annotation/Table.php b/src/Annotation/Table.php index 705dda56..a3007dc3 100644 --- a/src/Annotation/Table.php +++ b/src/Annotation/Table.php @@ -11,9 +11,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target({"CLASS", "ANNOTATION"}) */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] diff --git a/src/Annotation/Table/Index.php b/src/Annotation/Table/Index.php index d3e28f42..48cbc502 100644 --- a/src/Annotation/Table/Index.php +++ b/src/Annotation/Table/Index.php @@ -9,9 +9,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("ANNOTATION", "CLASS") */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] diff --git a/src/Annotation/Table/PrimaryKey.php b/src/Annotation/Table/PrimaryKey.php index 9fb68b88..c12d2cf5 100644 --- a/src/Annotation/Table/PrimaryKey.php +++ b/src/Annotation/Table/PrimaryKey.php @@ -9,9 +9,7 @@ /** * @Annotation - * * @NamedArgumentConstructor - * * @Target("ANNOTATION", "CLASS") */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] diff --git a/src/Configurator.php b/src/Configurator.php index e51fe0f7..6bb0a1e9 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -8,11 +8,13 @@ use Cycle\Annotated\Annotation\Embeddable; use Cycle\Annotated\Annotation\Entity; use Cycle\Annotated\Annotation\ForeignKey; +use Cycle\Annotated\Annotation\GeneratedValue; use Cycle\Annotated\Annotation\Relation as RelationAnnotation; use Cycle\Annotated\Exception\AnnotationException; use Cycle\Annotated\Exception\AnnotationRequiredArgumentsException; use Cycle\Annotated\Exception\AnnotationWrongTypeArgumentException; use Cycle\Annotated\Utils\EntityUtils; +use Cycle\ORM\Schema\GeneratedField; use Cycle\Schema\Definition\Entity as EntitySchema; use Cycle\Schema\Definition\ForeignKey as ForeignKeySchema; use Cycle\Schema\Definition\Field; @@ -22,7 +24,6 @@ use Doctrine\Common\Annotations\Reader as DoctrineReader; use Doctrine\Inflector\Inflector; use Doctrine\Inflector\Rules\English\InflectorFactory; -use Exception; use Spiral\Attributes\ReaderInterface; final class Configurator @@ -91,7 +92,7 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string foreach ($class->getProperties() as $property) { try { $column = $this->reader->firstPropertyMetadata($property, Column::class); - } catch (Exception $e) { + } catch (\Exception $e) { throw new AnnotationException($e->getMessage(), $e->getCode(), $e); } catch (\ArgumentCountError $e) { throw AnnotationRequiredArgumentsException::createFor($property, Column::class, $e); @@ -221,6 +222,9 @@ public function initField(string $name, Column $column, \ReflectionClass $class, $field->setType($column->getType()); $field->setColumn($columnPrefix . ($column->getColumn() ?? $this->inflector->tableize($name))); $field->setPrimary($column->isPrimary()); + if ($this->isOnInsertGeneratedField($field)) { + $field->setGenerated(GeneratedField::ON_INSERT); + } $field->setTypecast($this->resolveTypecast($column->getTypecast(), $class)); @@ -286,6 +290,20 @@ public function initForeignKeys(Entity $ann, EntitySchema $entity, \ReflectionCl } } + public function initGeneratedFields(EntitySchema $entity, \ReflectionClass $class): void + { + foreach ($class->getProperties() as $property) { + try { + $generated = $this->reader->firstPropertyMetadata($property, GeneratedValue::class); + if ($generated !== null) { + $entity->getFields()->get($property->getName())->setGenerated($generated->getFlags()); + } + } catch (\Throwable $e) { + throw new AnnotationException($e->getMessage(), (int) $e->getCode(), $e); + } + } + } + /** * Resolve class or role name relative to the current class. */ @@ -368,4 +386,12 @@ private function getPropertyMetadata(\ReflectionProperty $property, string $name throw new AnnotationException($e->getMessage(), $e->getCode(), $e); } } + + private function isOnInsertGeneratedField(Field $field): bool + { + return match ($field->getType()) { + 'serial', 'bigserial', 'smallserial' => true, + default => $field->isPrimary() + }; + } } diff --git a/src/Entities.php b/src/Entities.php index fec0d232..7fa327a5 100644 --- a/src/Entities.php +++ b/src/Entities.php @@ -84,6 +84,9 @@ public function run(Registry $registry): Registry continue; } + // generated fields + $this->generator->initGeneratedFields($e, $class); + // register entity (OR find parent) $registry->register($e); $registry->linkTable($e, $e->getDatabase(), $e->getTableName()); @@ -99,7 +102,7 @@ public function run(Registry $registry): Registry private function normalizeNames(Registry $registry): Registry { foreach ($this->locator->getClasses() as $class) { - if (! $registry->hasEntity($class->getName())) { + if (!$registry->hasEntity($class->getName())) { continue; } diff --git a/src/Exception/AnnotationRequiredArgumentsException.php b/src/Exception/AnnotationRequiredArgumentsException.php index 23f4321c..7330980b 100644 --- a/src/Exception/AnnotationRequiredArgumentsException.php +++ b/src/Exception/AnnotationRequiredArgumentsException.php @@ -12,7 +12,7 @@ public static function createFor(\ReflectionProperty $property, string $annotati $requiredArguments = []; foreach ($column->getConstructor()->getParameters() as $parameter) { - if (! $parameter->isOptional()) { + if (!$parameter->isOptional()) { $requiredArguments[] = $parameter->getName(); } } diff --git a/tests/Annotated/Fixtures/Fixtures25/PostgreSQL/WithGeneratedSerial.php b/tests/Annotated/Fixtures/Fixtures25/PostgreSQL/WithGeneratedSerial.php new file mode 100644 index 00000000..bfe0e4f7 --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures25/PostgreSQL/WithGeneratedSerial.php @@ -0,0 +1,39 @@ + [__DIR__ . '/../../../Fixtures/Fixtures25'], + 'exclude' => ['PostgreSQL'], + ])); + + $r = new Registry($this->dbal); + $schema = (new Compiler())->compile($r, [ + new Entities($tokenizer->classLocator(), $reader), + ]); + + $this->assertSame( + [ + 'id' => GeneratedField::ON_INSERT, + 'createdAt' => GeneratedField::BEFORE_INSERT, + 'createdAtGeneratedByDatabase' => GeneratedField::ON_INSERT, + 'updatedAt' => GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, + ], + $schema['withGeneratedFields'][SchemaInterface::GENERATED_FIELDS] + ); + } +} diff --git a/tests/Annotated/Functional/Driver/Common/Inheritance/SingleTableTest.php b/tests/Annotated/Functional/Driver/Common/Inheritance/SingleTableTest.php index 029f07fb..f31c75da 100644 --- a/tests/Annotated/Functional/Driver/Common/Inheritance/SingleTableTest.php +++ b/tests/Annotated/Functional/Driver/Common/Inheritance/SingleTableTest.php @@ -19,6 +19,7 @@ use Cycle\ORM\Relation; use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; +use Cycle\ORM\Schema\GeneratedField; use Cycle\ORM\Select\Repository; use Cycle\ORM\Select\Source; use Cycle\ORM\Transaction; @@ -312,6 +313,9 @@ public function testSingleTableInheritanceWithDifferentColumnDeclaration( ], SchemaInterface::SCHEMA => [], SchemaInterface::TYPECAST_HANDLER => null, + SchemaInterface::GENERATED_FIELDS => [ + 'id' => GeneratedField::ON_INSERT, + ], ], $schema['comment'] ); diff --git a/tests/Annotated/Functional/Driver/Common/TableTest.php b/tests/Annotated/Functional/Driver/Common/TableTest.php index ff1a02fb..c20a04fc 100644 --- a/tests/Annotated/Functional/Driver/Common/TableTest.php +++ b/tests/Annotated/Functional/Driver/Common/TableTest.php @@ -157,7 +157,7 @@ public function testCompositePrimaryKey(ReaderInterface $reader): void */ public function testIndexWithEmptyColumnsShouldThrowAnException(ReaderInterface $reader): void { - $this->expectException(\Cycle\Annotated\Exception\AnnotationException::class); + $this->expectException(AnnotationException::class); $this->expectExceptionMessage('Invalid index definition for `compositePost`. Column list can\'t be empty.'); $r = new Registry($this->dbal); diff --git a/tests/Annotated/Functional/Driver/MySQL/GeneratedFieldsTest.php b/tests/Annotated/Functional/Driver/MySQL/GeneratedFieldsTest.php new file mode 100644 index 00000000..88cf91ec --- /dev/null +++ b/tests/Annotated/Functional/Driver/MySQL/GeneratedFieldsTest.php @@ -0,0 +1,17 @@ + [__DIR__ . '/../../../Fixtures/Fixtures25/PostgreSQL'], + 'exclude' => [], + ])); + + $r = new Registry($this->dbal); + + $schema = (new Compiler())->compile($r, [ + new Entities($tokenizer->classLocator(), $reader), + ]); + + $this->assertSame( + [ + 'id' => GeneratedField::ON_INSERT, + 'smallSerial' => GeneratedField::ON_INSERT, + 'serial' => GeneratedField::ON_INSERT, + 'bigSerial' => GeneratedField::ON_INSERT, + ], + $schema['withGeneratedSerial'][SchemaInterface::GENERATED_FIELDS] + ); + } +} diff --git a/tests/Annotated/Functional/Driver/SQLServer/GeneratedFieldsTest.php b/tests/Annotated/Functional/Driver/SQLServer/GeneratedFieldsTest.php new file mode 100644 index 00000000..e58c8a00 --- /dev/null +++ b/tests/Annotated/Functional/Driver/SQLServer/GeneratedFieldsTest.php @@ -0,0 +1,17 @@ +