From 7dad356336ee70ef1f6e7b750274d4d215a691f1 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Thu, 8 Feb 2024 23:45:04 +0200 Subject: [PATCH] Support for generated fields (#95) Co-authored-by: roxblnfk --- composer.json | 4 +- src/Annotation/GeneratedValue.php | 37 +++++++++++++ src/Configurator.php | 29 +++++++++- src/Entities.php | 3 ++ .../PostgreSQL/WithGeneratedSerial.php | 39 ++++++++++++++ .../Fixtures25/WithGeneratedFields.php | 52 ++++++++++++++++++ .../Driver/Common/GeneratedFieldsTestCase.php | 45 ++++++++++++++++ .../Inheritance/SingleTableTestCase.php | 4 ++ .../Driver/MySQL/GeneratedFieldsTest.php | 16 ++++++ .../Driver/Postgres/GeneratedFieldsTest.php | 53 +++++++++++++++++++ .../Driver/SQLServer/GeneratedFieldsTest.php | 16 ++++++ .../Driver/SQLite/GeneratedFieldsTest.php | 16 ++++++ 12 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 src/Annotation/GeneratedValue.php create mode 100644 tests/Annotated/Fixtures/Fixtures25/PostgreSQL/WithGeneratedSerial.php create mode 100644 tests/Annotated/Fixtures/Fixtures25/WithGeneratedFields.php create mode 100644 tests/Annotated/Functional/Driver/Common/GeneratedFieldsTestCase.php create mode 100644 tests/Annotated/Functional/Driver/MySQL/GeneratedFieldsTest.php create mode 100644 tests/Annotated/Functional/Driver/Postgres/GeneratedFieldsTest.php create mode 100644 tests/Annotated/Functional/Driver/SQLServer/GeneratedFieldsTest.php create mode 100644 tests/Annotated/Functional/Driver/SQLite/GeneratedFieldsTest.php diff --git a/composer.json b/composer.json index d75f7a96..e4ed345c 100644 --- a/composer.json +++ b/composer.json @@ -38,8 +38,8 @@ ], "require": { "php": ">=8.1", - "cycle/orm": "^2.2.0", - "cycle/schema-builder": "^2.6", + "cycle/orm": "^2.7", + "cycle/schema-builder": "^2.8", "spiral/attributes": "^2.8|^3.0", "spiral/tokenizer": "^2.8|^3.0", "doctrine/inflector": "^2.0" 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/Configurator.php b/src/Configurator.php index a679040e..e5b06ebe 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -8,14 +8,16 @@ 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; +use Cycle\Schema\Definition\ForeignKey as ForeignKeySchema; use Cycle\Schema\Definition\Relation; use Cycle\Schema\Generator\SyncTables; use Cycle\Schema\SchemaModifierInterface; @@ -232,6 +234,9 @@ public function initField(string $name, Column $column, \ReflectionClass $class, $field->setColumn($columnName); $field->setPrimary($column->isPrimary()); + if ($this->isOnInsertGeneratedField($field)) { + $field->setGenerated(GeneratedField::ON_INSERT); + } $field->setTypecast($this->resolveTypecast($column->getTypecast(), $class)); @@ -297,6 +302,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. * @@ -386,4 +405,12 @@ private function getPropertyMetadata(\ReflectionProperty $property, string $name throw new AnnotationException($e->getMessage(), (int) $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 bcfca3c0..0e6b8567 100644 --- a/src/Entities.php +++ b/src/Entities.php @@ -77,6 +77,9 @@ public function run(Registry $registry): Registry continue; } + // generated fields + $this->generator->initGeneratedFields($e, $entity->class); + // register entity (OR find parent) $registry->register($e); 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'], + ])); + + $locator = $tokenizer->classLocator(); + + $r = new Registry($this->dbal); + $schema = (new Compiler())->compile($r, [ + new Entities(new TokenizerEntityLocator($locator, $reader), $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/SingleTableTestCase.php b/tests/Annotated/Functional/Driver/Common/Inheritance/SingleTableTestCase.php index 67d06cb9..bd620e8b 100644 --- a/tests/Annotated/Functional/Driver/Common/Inheritance/SingleTableTestCase.php +++ b/tests/Annotated/Functional/Driver/Common/Inheritance/SingleTableTestCase.php @@ -21,6 +21,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\EntityManager; @@ -306,6 +307,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/MySQL/GeneratedFieldsTest.php b/tests/Annotated/Functional/Driver/MySQL/GeneratedFieldsTest.php new file mode 100644 index 00000000..30f2c995 --- /dev/null +++ b/tests/Annotated/Functional/Driver/MySQL/GeneratedFieldsTest.php @@ -0,0 +1,16 @@ + [__DIR__ . '/../../../Fixtures/Fixtures25/PostgreSQL'], + 'exclude' => [], + ])); + + $locator = $tokenizer->classLocator(); + + $r = new Registry($this->dbal); + + $schema = (new Compiler())->compile($r, [ + new Entities(new TokenizerEntityLocator($locator, $reader), $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..3c3759d2 --- /dev/null +++ b/tests/Annotated/Functional/Driver/SQLServer/GeneratedFieldsTest.php @@ -0,0 +1,16 @@ +