diff --git a/composer.json b/composer.json index 07b7333..3941428 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", diff --git a/src/Annotated/Locator/ListenerEmbeddingsLocator.php b/src/Annotated/Locator/ListenerEmbeddingsLocator.php new file mode 100644 index 0000000..0718827 --- /dev/null +++ b/src/Annotated/Locator/ListenerEmbeddingsLocator.php @@ -0,0 +1,53 @@ +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..b9be91c --- /dev/null +++ b/src/Annotated/Locator/ListenerEntityLocator.php @@ -0,0 +1,54 @@ +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..1fad6ae 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,6 +28,11 @@ final class AnnotatedBootloader extends Bootloader Annotated\MergeIndexes::class => [self::class, 'initMergeIndexes'], ]; + protected const SINGLETONS = [ + ListenerEntityLocator::class => ListenerEntityLocator::class, + ListenerEmbeddingsLocator::class => ListenerEmbeddingsLocator::class, + ]; + public function init(SchemaBootloader $schema): void { $schema->addGenerator(SchemaBootloader::GROUP_INDEX, Annotated\Embeddings::class); @@ -36,14 +42,25 @@ 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); + public function boot( + TokenizerListenerBootloader $tokenizer, + ListenerEntityLocator $entityLocator, + ListenerEmbeddingsLocator $embeddingsLocator + ): void { + $tokenizer->addListener($entityLocator); + $tokenizer->addListener($embeddingsLocator); } - public function initEntities(ClassesInterface $classes, ReaderInterface $reader): Annotated\Entities + private function initEmbeddings( + ReaderInterface $reader, + ListenerEmbeddingsLocator $embeddingsLocator + ): Annotated\Embeddings { + return new Annotated\Embeddings($embeddingsLocator, $reader); + } + + 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 +78,3 @@ public function initMergeIndexes(ReaderInterface $reader): Annotated\MergeIndexe return new Annotated\MergeIndexes($reader); } } - diff --git a/src/Bootloader/PrototypeBootloader.php b/src/Bootloader/PrototypeBootloader.php index 440fca6..52f09f9 100644 --- a/src/Bootloader/PrototypeBootloader.php +++ b/src/Bootloader/PrototypeBootloader.php @@ -9,15 +9,16 @@ use Cycle\ORM; use Doctrine\Inflector\Rules\English\InflectorFactory; use Psr\Container\ContainerInterface; +use Spiral\Boot\AbstractKernel; use Spiral\Boot\Bootloader\Bootloader; use Spiral\Prototype\Bootloader\PrototypeBootloader as BasePrototypeBootloader; final class PrototypeBootloader extends Bootloader { - public function boot(BasePrototypeBootloader $prototype, ContainerInterface $container): void + public function boot(AbstractKernel $kernel): void { - $this->bindDatabase($prototype); - $this->bindCycle($prototype, $container); + $kernel->bootstrapped($this->bindDatabase(...)); + $kernel->bootstrapped($this->bindCycle(...)); } private function bindDatabase(BasePrototypeBootloader $prototype): void 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 7961e3b..61c1633 100644 --- a/tests/src/Console/Command/CycleOrm/MigrateCommandTest.php +++ b/tests/src/Console/Command/CycleOrm/MigrateCommandTest.php @@ -8,6 +8,7 @@ use Cycle\Annotated\Annotation\Entity; 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; @@ -76,6 +77,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',