diff --git a/composer.json b/composer.json index a80fbf6..17a23e0 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": ">=8.1", - "cycle/annotated": "^3.1", + "cycle/annotated": "^4.0", "cycle/migrations": "^4.0.1", "cycle/orm": "^2.0.2", "cycle/schema-migrations-generator": "^2.1", @@ -37,7 +37,6 @@ "spiral/auth": "^3.0", "spiral/tokenizer": "^3.0", "spiral/config": "^3.0", - "spiral/validator": "^1.2", "spiral/filters": "^3.10", "spiral/data-grid-bridge": "^3.0", "psr/container": "^1.1 || ^2.0" @@ -48,8 +47,9 @@ "infection/infection": "^0.26.6", "mockery/mockery": "^1.5", "phpunit/phpunit": "^9.5.20", - "spiral/framework": "^3.9", + "spiral/framework": "^3.11.1", "spiral/testing": "^2.4", + "spiral/validator": "^1.5", "spiral/nyholm-bridge": "^1.3", "spiral-packages/database-seeder": "^3.1", "vimeo/psalm": "^4.27" diff --git a/src/Annotated/Locator/ListenerEmbeddingsLocator.php b/src/Annotated/Locator/ListenerEmbeddingsLocator.php new file mode 100644 index 0000000..d5a35d9 --- /dev/null +++ b/src/Annotated/Locator/ListenerEmbeddingsLocator.php @@ -0,0 +1,55 @@ +reader->firstClassMetadata($class, Embeddable::class); + } catch (\Exception $e) { + throw new AnnotationException($e->getMessage(), (int) $e->getCode(), $e); + } + + if ($attribute !== null) { + $this->embeddings[] = new Embedding($attribute, $class); + } + } + + public function finalize(): void + { + $this->collected = true; + } + + public function getEmbeddings(): array + { + if (!$this->collected) { + throw new AnnotationException(\sprintf('Tokenizer did not finalize %s listener.', self::class)); + } + + return $this->embeddings; + } +} diff --git a/src/Annotated/Locator/ListenerEntityLocator.php b/src/Annotated/Locator/ListenerEntityLocator.php new file mode 100644 index 0000000..710006d --- /dev/null +++ b/src/Annotated/Locator/ListenerEntityLocator.php @@ -0,0 +1,56 @@ +reader->firstClassMetadata($class, Attribute::class); + } catch (\Exception $e) { + throw new AnnotationException($e->getMessage(), (int) $e->getCode(), $e); + } + + if ($attribute !== null) { + $this->entities[] = new Entity($attribute, $class); + } + } + + public function finalize(): void + { + $this->collected = true; + } + + public function getEntities(): array + { + if (!$this->collected) { + throw new AnnotationException(\sprintf('Tokenizer did not finalize %s listener.', self::class)); + } + + return $this->entities; + } +} diff --git a/src/Bootloader/AnnotatedBootloader.php b/src/Bootloader/AnnotatedBootloader.php index ba0b45c..f22d332 100644 --- a/src/Bootloader/AnnotatedBootloader.php +++ b/src/Bootloader/AnnotatedBootloader.php @@ -8,14 +8,15 @@ use Spiral\Attributes\ReaderInterface; use Spiral\Boot\Bootloader\Bootloader; use Spiral\Bootloader\Attributes\AttributesBootloader; -use Spiral\Tokenizer\Bootloader\TokenizerBootloader; -use Spiral\Tokenizer\ClassesInterface; +use Spiral\Cycle\Annotated\Locator\ListenerEmbeddingsLocator; +use Spiral\Cycle\Annotated\Locator\ListenerEntityLocator; +use Spiral\Tokenizer\Bootloader\TokenizerListenerBootloader; final class AnnotatedBootloader extends Bootloader { protected const DEPENDENCIES = [ SchemaBootloader::class, - TokenizerBootloader::class, + TokenizerListenerBootloader::class, AttributesBootloader::class, ]; @@ -27,8 +28,20 @@ final class AnnotatedBootloader extends Bootloader Annotated\MergeIndexes::class => [self::class, 'initMergeIndexes'], ]; - public function init(SchemaBootloader $schema): void - { + protected const SINGLETONS = [ + ListenerEntityLocator::class => ListenerEntityLocator::class, + ListenerEmbeddingsLocator::class => ListenerEmbeddingsLocator::class, + ]; + + public function init( + SchemaBootloader $schema, + TokenizerListenerBootloader $tokenizer, + ListenerEntityLocator $entityLocator, + ListenerEmbeddingsLocator $embeddingsLocator + ): void { + $tokenizer->addListener($entityLocator); + $tokenizer->addListener($embeddingsLocator); + $schema->addGenerator(SchemaBootloader::GROUP_INDEX, Annotated\Embeddings::class); $schema->addGenerator(SchemaBootloader::GROUP_INDEX, Annotated\Entities::class); $schema->addGenerator(SchemaBootloader::GROUP_INDEX, Annotated\TableInheritance::class); @@ -36,14 +49,16 @@ public function init(SchemaBootloader $schema): void $schema->addGenerator(SchemaBootloader::GROUP_RENDER, Annotated\MergeIndexes::class); } - private function initEmbeddings(ClassesInterface $classes, ReaderInterface $reader): Annotated\Embeddings - { - return new Annotated\Embeddings($classes, $reader); + private function initEmbeddings( + ReaderInterface $reader, + ListenerEmbeddingsLocator $embeddingsLocator + ): Annotated\Embeddings { + return new Annotated\Embeddings($embeddingsLocator, $reader); } - public function initEntities(ClassesInterface $classes, ReaderInterface $reader): Annotated\Entities + public function initEntities(ReaderInterface $reader, ListenerEntityLocator $entityLocator): Annotated\Entities { - return new Annotated\Entities($classes, $reader); + return new Annotated\Entities($entityLocator, $reader); } public function initMergeColumns(ReaderInterface $reader): Annotated\MergeColumns @@ -61,4 +76,3 @@ public function initMergeIndexes(ReaderInterface $reader): Annotated\MergeIndexe return new Annotated\MergeIndexes($reader); } } - diff --git a/tests/app/Entities/Address.php b/tests/app/Entities/Address.php new file mode 100644 index 0000000..e61d44a --- /dev/null +++ b/tests/app/Entities/Address.php @@ -0,0 +1,21 @@ +listen(new \ReflectionClass(Address::class)); + $locator->finalize(); + + $this->assertEquals( + [ + new Embedding( + new Embeddable(), + new \ReflectionClass(Address::class) + ), + ], + $locator->getEmbeddings()); + } + + public function testListenWithoutAttribute(): void + { + $locator = new ListenerEmbeddingsLocator(new AttributeReader()); + $locator->listen(new \ReflectionClass(User::class)); + $locator->finalize(); + + $this->assertSame([], $locator->getEmbeddings()); + } + + public function testGetEmbeddingsWithoutFinalize(): void + { + $this->expectException(AnnotationException::class); + $this->expectExceptionMessage( + \sprintf('Tokenizer did not finalize %s listener.', ListenerEmbeddingsLocator::class) + ); + + $locator = new ListenerEmbeddingsLocator(new AttributeReader()); + $locator->getEmbeddings(); + } +} diff --git a/tests/src/Annotated/Locator/ListenerEntityLocatorTest.php b/tests/src/Annotated/Locator/ListenerEntityLocatorTest.php new file mode 100644 index 0000000..8b8487c --- /dev/null +++ b/tests/src/Annotated/Locator/ListenerEntityLocatorTest.php @@ -0,0 +1,52 @@ +listen(new \ReflectionClass(User::class)); + $locator->finalize(); + + $this->assertEquals( + [ + new Entity( + new \Cycle\Annotated\Annotation\Entity(repository: UserRepository::class), + new \ReflectionClass(User::class) + ), + ], + $locator->getEntities()); + } + + public function testListenWithoutAttribute(): void + { + $locator = new ListenerEntityLocator(new AttributeReader()); + $locator->listen(new \ReflectionClass(\stdClass::class)); + $locator->finalize(); + + $this->assertSame([], $locator->getEntities()); + } + + public function testGetEntitiesWithoutFinalize(): void + { + $this->expectException(AnnotationException::class); + $this->expectExceptionMessage( + \sprintf('Tokenizer did not finalize %s listener.', ListenerEntityLocator::class) + ); + + $locator = new ListenerEntityLocator(new AttributeReader()); + $locator->getEntities(); + } +} diff --git a/tests/src/Bootloader/AnnotatedBootloaderTest.php b/tests/src/Bootloader/AnnotatedBootloaderTest.php index 10d9883..81cf550 100644 --- a/tests/src/Bootloader/AnnotatedBootloaderTest.php +++ b/tests/src/Bootloader/AnnotatedBootloaderTest.php @@ -7,6 +7,8 @@ use Cycle\Annotated; use Cycle\Schema\GeneratorInterface; use Spiral\Attributes\ReaderInterface; +use Spiral\Cycle\Annotated\Locator\ListenerEmbeddingsLocator; +use Spiral\Cycle\Annotated\Locator\ListenerEntityLocator; use Spiral\Tests\BaseTest; final class AnnotatedBootloaderTest extends BaseTest @@ -40,4 +42,14 @@ public function testGetsAnnotatedMergeIndexes(): void { $this->assertContainerBound(Annotated\MergeIndexes::class, GeneratorInterface::class); } + + public function testGetsListenerEntityLocator(): void + { + $this->assertContainerBoundAsSingleton(ListenerEntityLocator::class, ListenerEntityLocator::class); + } + + public function testGetsListenerEmbeddingsLocator(): void + { + $this->assertContainerBoundAsSingleton(ListenerEmbeddingsLocator::class, ListenerEmbeddingsLocator::class); + } } diff --git a/tests/src/Console/Command/CycleOrm/MigrateCommandTest.php b/tests/src/Console/Command/CycleOrm/MigrateCommandTest.php index d913d86..051b2b5 100644 --- a/tests/src/Console/Command/CycleOrm/MigrateCommandTest.php +++ b/tests/src/Console/Command/CycleOrm/MigrateCommandTest.php @@ -6,6 +6,7 @@ use Cycle\ORM\SchemaInterface; use Spiral\Boot\MemoryInterface; +use Spiral\Cycle\Annotated\Locator\ListenerEntityLocator; use Spiral\Cycle\Config\CycleConfig; use Spiral\Files\Files; use Spiral\Tests\ConsoleTest; @@ -95,6 +96,9 @@ class Tag PHP, ); + $listener = $this->getContainer()->get(ListenerEntityLocator::class); + $listener->listen(new \ReflectionClass(\Spiral\App\Entities\Tag::class)); + $this->assertConsoleCommandOutputContainsStrings('cycle:migrate', ['-r' => true], [ 'default.tags', 'create table',