From 8ad4ffe64ca711146b84d64b4cd91c6300830115 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 20 May 2024 23:48:49 +0400 Subject: [PATCH 1/2] Add DeclarationDto; refactor DeclarationLocator --- src/Bootloader/TemporalBridgeBootloader.php | 6 +-- src/Declaration/DeclarationDto.php | 15 ++++++ src/Declaration/DeclarationType.php | 11 +++++ src/DeclarationLocator.php | 51 +++++++++++++++++---- src/DeclarationLocatorInterface.php | 1 + src/DeclarationRegistryInterface.php | 13 +++++- src/Dispatcher.php | 22 ++++----- tests/src/DeclarationLocatorTest.php | 42 +++++++++++++++++ tests/src/DispatcherTest.php | 16 +++---- 9 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 src/Declaration/DeclarationDto.php create mode 100644 src/Declaration/DeclarationType.php diff --git a/src/Bootloader/TemporalBridgeBootloader.php b/src/Bootloader/TemporalBridgeBootloader.php index fc27bdf..415b58e 100644 --- a/src/Bootloader/TemporalBridgeBootloader.php +++ b/src/Bootloader/TemporalBridgeBootloader.php @@ -64,11 +64,11 @@ public function defineSingletons(): array rpc: Goridge::create(), ), WorkerFactoryInterface::class => WorkerFactory::class, - DeclarationLocatorInterface::class => DeclarationRegistryInterface::class, - - DeclarationRegistryInterface::class => static fn() => new DeclarationLocator( + DeclarationLocator::class => new DeclarationLocator( reader: new AttributeReader(), ), + DeclarationLocatorInterface::class => DeclarationLocator::class, + DeclarationRegistryInterface::class => DeclarationLocator::class, WorkflowClientInterface::class => static fn( TemporalConfig $config, diff --git a/src/Declaration/DeclarationDto.php b/src/Declaration/DeclarationDto.php new file mode 100644 index 0000000..ab28ee4 --- /dev/null +++ b/src/Declaration/DeclarationDto.php @@ -0,0 +1,15 @@ + */ private array $declarations = []; public function __construct( @@ -23,17 +29,28 @@ public function __construct( ) { } - public function addDeclaration(\ReflectionClass|string $class): void + public function addDeclaration(DeclarationDto|\ReflectionClass|string $class): void { + if ($class instanceof DeclarationDto) { + $this->declarations[] = $class; + return; + } + $this->listen($class instanceof \ReflectionClass ? $class : new \ReflectionClass($class)); } + public function getDeclarationList(): iterable + { + return $this->declarations; + } + public function getDeclarations(): iterable { - foreach ($this->declarations as $type => $classes) { - foreach ($classes as $class) { - yield $type => $class; - } + foreach ($this->declarations as $declaration) { + yield match($declaration->type) { + DeclarationType::Workflow => WorkflowInterface::class, + DeclarationType::Activity => ActivityInterface::class, + } => $declaration->class; } } @@ -43,13 +60,29 @@ public function listen(\ReflectionClass $class): void return; } + /** @var DeclarationType|null $type */ + $type = null; + foreach (\array_merge($class->getInterfaces(), [$class]) as $type) { if ($this->reader->firstClassMetadata($type, WorkflowInterface::class) !== null) { - $this->declarations[WorkflowInterface::class][] = $class; - } elseif ($this->reader->firstClassMetadata($type, ActivityInterface::class) !== null) { - $this->declarations[ActivityInterface::class][] = $class; + $type = DeclarationType::Workflow; + break; + } + + if ($this->reader->firstClassMetadata($type, ActivityInterface::class) !== null) { + $type = DeclarationType::Activity; + break; } } + + if ($type !== null) { + $this->declarations[] = new DeclarationDto( + type: $type, + class: $class, + taskQueue: null, + ); + } + } public function finalize(): void diff --git a/src/DeclarationLocatorInterface.php b/src/DeclarationLocatorInterface.php index ff0c393..56a07b4 100644 --- a/src/DeclarationLocatorInterface.php +++ b/src/DeclarationLocatorInterface.php @@ -16,6 +16,7 @@ interface DeclarationLocatorInterface * List of all declarations for workflows and activities. * * @return iterable|class-string, \ReflectionClass> + * @deprecated Use {@see DeclarationRegistryInterface::getDeclarationList} instead. */ public function getDeclarations(): iterable; } diff --git a/src/DeclarationRegistryInterface.php b/src/DeclarationRegistryInterface.php index 0091a1d..21803b0 100644 --- a/src/DeclarationRegistryInterface.php +++ b/src/DeclarationRegistryInterface.php @@ -4,12 +4,21 @@ namespace Spiral\TemporalBridge; -interface DeclarationRegistryInterface extends DeclarationLocatorInterface +use Spiral\TemporalBridge\Declaration\DeclarationDto; + +interface DeclarationRegistryInterface { /** * Add a new declaration to the registry. * * @param \ReflectionClass|class-string $class Workflow or activity class name or reflection. */ - public function addDeclaration(\ReflectionClass|string $class): void; + public function addDeclaration(DeclarationDto|\ReflectionClass|string $class): void; + + /** + * List all declarations. + * + * @return iterable + */ + public function getDeclarationList(): iterable; } diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 0a750ad..421f3c4 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -8,9 +8,9 @@ use Spiral\Boot\DispatcherInterface; use Spiral\Core\Container; use Spiral\RoadRunnerBridge\RoadRunnerMode; -use Temporal\Activity\ActivityInterface; +use Spiral\TemporalBridge\Declaration\DeclarationDto; +use Spiral\TemporalBridge\Declaration\DeclarationType; use Temporal\Worker\WorkerFactoryInterface; -use Temporal\Workflow\WorkflowInterface; final class Dispatcher implements DispatcherInterface { @@ -29,10 +29,8 @@ public function canServe(): bool public function serve(): void { // finds all available workflows, activity types and commands in a given directory - /** - * @var array|class-string, ReflectionClass> $declarations - */ - $declarations = $this->container->get(DeclarationRegistryInterface::class)->getDeclarations(); + /** @var list $declarations */ + $declarations = $this->container->get(DeclarationRegistryInterface::class)->getDeclarationList(); // factory initiates and runs task queue specific activity and workflow workers /** @var WorkerFactoryInterface $factory */ @@ -41,22 +39,22 @@ public function serve(): void $registry = $this->container->get(WorkersRegistryInterface::class); $hasDeclarations = false; - foreach ($declarations as $type => $declaration) { + foreach ($declarations as $declaration) { // Worker that listens on a task queue and hosts both workflow and activity implementations. - $taskQueues = $this->workerResolver->resolve($declaration); + $taskQueues = $this->workerResolver->resolve($declaration->class); foreach ($taskQueues as $taskQueue) { $worker = $registry->get($taskQueue); - if ($type === WorkflowInterface::class) { + if ($declaration->type === DeclarationType::Workflow) { // Workflows are stateful. So you need a type to create instances. - $worker->registerWorkflowTypes($declaration->getName()); + $worker->registerWorkflowTypes($declaration->class->getName()); } - if ($type === ActivityInterface::class) { + if ($declaration->type === DeclarationType::Activity) { // Workflows are stateful. So you need a type to create instances. $worker->registerActivity( - $declaration->getName(), + $declaration->class->getName(), fn(ReflectionClass $class): object => $this->container->make($class->getName()), ); } diff --git a/tests/src/DeclarationLocatorTest.php b/tests/src/DeclarationLocatorTest.php index 9f98ab6..f0524b5 100644 --- a/tests/src/DeclarationLocatorTest.php +++ b/tests/src/DeclarationLocatorTest.php @@ -5,6 +5,8 @@ namespace Spiral\TemporalBridge\Tests; use Spiral\Attributes\AttributeReader; +use Spiral\TemporalBridge\Declaration\DeclarationDto; +use Spiral\TemporalBridge\Declaration\DeclarationType; use Spiral\TemporalBridge\DeclarationLocator; use Temporal\Activity\ActivityInterface; use Temporal\Workflow\WorkflowInterface; @@ -139,6 +141,46 @@ public function testAddDeclarationClassNames(): void $this->assertSame(ActivityInterface::class, $result[3][0]); $this->assertSame(TestActivityClassWithInterface::class, $result[3][1]->getName()); } + + public function testAddDeclarationDto(): void + { + $this->locator->addDeclaration(new DeclarationDto( + type: DeclarationType::Workflow, + class: new \ReflectionClass(TestWorkflowClass::class), + )); + $this->locator->addDeclaration(new DeclarationDto( + type: DeclarationType::Workflow, + class: new \ReflectionClass(TestWorkflowClassWithInterface::class), + )); + $this->locator->addDeclaration(new DeclarationDto( + type: DeclarationType::Activity, + class: new \ReflectionClass(TestActivityClass::class), + )); + $this->locator->addDeclaration(new DeclarationDto( + type: DeclarationType::Activity, + class: new \ReflectionClass(TestActivityClassWithInterface::class), + )); + + $result = []; + + foreach ($this->locator->getDeclarations() as $type => $class) { + $result[] = [$type, $class]; + } + + $this->assertCount(4, $result); + + $this->assertSame(WorkflowInterface::class, $result[0][0]); + $this->assertSame(TestWorkflowClass::class, $result[0][1]->getName()); + + $this->assertSame(WorkflowInterface::class, $result[1][0]); + $this->assertSame(TestWorkflowClassWithInterface::class, $result[1][1]->getName()); + + $this->assertSame(ActivityInterface::class, $result[2][0]); + $this->assertSame(TestActivityClass::class, $result[2][1]->getName()); + + $this->assertSame(ActivityInterface::class, $result[3][0]); + $this->assertSame(TestActivityClassWithInterface::class, $result[3][1]->getName()); + } } enum TestEnum diff --git a/tests/src/DispatcherTest.php b/tests/src/DispatcherTest.php index b0f5f0c..3fff41b 100644 --- a/tests/src/DispatcherTest.php +++ b/tests/src/DispatcherTest.php @@ -8,6 +8,8 @@ use Spiral\Attributes\AttributeReader; use Spiral\RoadRunnerBridge\RoadRunnerMode; use Spiral\TemporalBridge\Config\TemporalConfig; +use Spiral\TemporalBridge\Declaration\DeclarationDto; +use Spiral\TemporalBridge\Declaration\DeclarationType; use Spiral\TemporalBridge\DeclarationRegistryInterface; use Spiral\TemporalBridge\DeclarationWorkerResolver; use Spiral\TemporalBridge\Dispatcher; @@ -16,10 +18,8 @@ use Spiral\TemporalBridge\Tests\App\SomeWorkflow; use Spiral\TemporalBridge\Tests\App\SomeWorkflowWithMultipleWorkers; use Spiral\TemporalBridge\WorkersRegistryInterface; -use Temporal\Activity\ActivityInterface; use Temporal\Worker\WorkerFactoryInterface; use Temporal\Worker\WorkerInterface; -use Temporal\Workflow\WorkflowInterface; final class DispatcherTest extends TestCase { @@ -42,7 +42,7 @@ protected function setUp(): void public function testServeWithoutDeclarations(): void { $locator = $this->mockContainer(DeclarationRegistryInterface::class); - $locator->shouldReceive('getDeclarations')->once()->andReturn([]); + $locator->shouldReceive('getDeclarationList')->once()->andReturn([]); $registry = $this->mockContainer(WorkersRegistryInterface::class); $registry @@ -60,11 +60,11 @@ public function testServeWithoutDeclarations(): void public function testServeWithDeclarations(): void { $locator = $this->mockContainer(DeclarationRegistryInterface::class); - $locator->shouldReceive('getDeclarations')->once()->andReturnUsing(function () { - yield WorkflowInterface::class => new \ReflectionClass(SomeWorkflow::class); - yield WorkflowInterface::class => new \ReflectionClass(SomeWorkflowWithMultipleWorkers::class); - yield ActivityInterface::class => new \ReflectionClass(SomeActivity::class); - yield ActivityInterface::class => new \ReflectionClass(SomeActivityWithDefaultWorker::class); + $locator->shouldReceive('getDeclarationList')->once()->andReturnUsing(function () { + yield new DeclarationDto(DeclarationType::Workflow, new \ReflectionClass(SomeWorkflow::class)); + yield new DeclarationDto(DeclarationType::Workflow, new \ReflectionClass(SomeWorkflowWithMultipleWorkers::class)); + yield new DeclarationDto(DeclarationType::Activity, new \ReflectionClass(SomeActivity::class)); + yield new DeclarationDto(DeclarationType::Activity, new \ReflectionClass(SomeActivityWithDefaultWorker::class)); }); $registry = $this->mockContainer(WorkersRegistryInterface::class); From 2f5d4bb370cd72547354f24a73408eba7b3578c8 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 21 May 2024 00:22:15 +0400 Subject: [PATCH 2/2] Info command now works with DeclarationRegistryInterface instead of deprecated DeclarationLocatorInterface --- src/Bootloader/TemporalBridgeBootloader.php | 2 +- src/Commands/InfoCommand.php | 28 +++++++++++---------- src/Dispatcher.php | 4 ++- tests/src/Commands/InfoCommandTest.php | 28 +++++++++++++++------ 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/Bootloader/TemporalBridgeBootloader.php b/src/Bootloader/TemporalBridgeBootloader.php index 415b58e..3de448c 100644 --- a/src/Bootloader/TemporalBridgeBootloader.php +++ b/src/Bootloader/TemporalBridgeBootloader.php @@ -64,7 +64,7 @@ public function defineSingletons(): array rpc: Goridge::create(), ), WorkerFactoryInterface::class => WorkerFactory::class, - DeclarationLocator::class => new DeclarationLocator( + DeclarationLocator::class => static fn (): DeclarationLocator => new DeclarationLocator( reader: new AttributeReader(), ), DeclarationLocatorInterface::class => DeclarationLocator::class, diff --git a/src/Commands/InfoCommand.php b/src/Commands/InfoCommand.php index 61efe46..dad4723 100644 --- a/src/Commands/InfoCommand.php +++ b/src/Commands/InfoCommand.php @@ -8,13 +8,13 @@ use Spiral\Console\Attribute\AsCommand; use Spiral\Console\Attribute\Option; use Spiral\Console\Command; -use Spiral\TemporalBridge\DeclarationLocatorInterface; +use Spiral\TemporalBridge\Declaration\DeclarationType; +use Spiral\TemporalBridge\DeclarationRegistryInterface; use Spiral\TemporalBridge\DeclarationWorkerResolver; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Output\OutputInterface; use Temporal\Internal\Declaration\Reader\ActivityReader; use Temporal\Internal\Declaration\Reader\WorkflowReader; -use Temporal\Workflow\WorkflowInterface; #[AsCommand( name: 'temporal:info', @@ -26,7 +26,7 @@ final class InfoCommand extends Command private bool $showActivities = false; public function perform( - DeclarationLocatorInterface $locator, + DeclarationRegistryInterface $registry, DeclarationWorkerResolver $workerResolver, WorkflowReader $workflowReader, ActivityReader $activityReader, @@ -35,25 +35,27 @@ public function perform( $workflows = []; $activities = []; - foreach ($locator->getDeclarations() as $type => $declaration) { - $taskQueue = $workerResolver->resolve($declaration); + foreach ($registry->getDeclarationList() as $declaration) { + $taskQueue = $declaration->taskQueue === null + ? $workerResolver->resolve($declaration->class) + : [$declaration->taskQueue]; - if ($type === WorkflowInterface::class) { - $prototype = $workflowReader->fromClass($declaration->getName()); + if ($declaration->type === DeclarationType::Workflow) { + $prototype = $workflowReader->fromClass($declaration->class->getName()); $workflows[$prototype->getID()] = [ - 'class' => $declaration->getName(), - 'file' => $declaration->getFileName(), + 'class' => $declaration->class->getName(), + 'file' => $declaration->class->getFileName(), 'name' => $prototype->getID(), 'task_queue' => \implode(', ', $taskQueue), ]; } else { $taskQueueShown = false; - foreach ($activityReader->fromClass($declaration->getName()) as $prototype) { - $activities[$declaration->getName()][$prototype->getID()] = [ - 'file' => $declaration->getFileName(), + foreach ($activityReader->fromClass($declaration->class->getName()) as $prototype) { + $activities[$declaration->class->getName()][$prototype->getID()] = [ + 'file' => $declaration->class->getFileName(), 'name' => $prototype->getID(), - 'handler' => $declaration->getShortName() . '::' . $prototype->getHandler()->getName(), + 'handler' => $declaration->class->getShortName() . '::' . $prototype->getHandler()->getName(), 'task_queue' => !$taskQueueShown ? \implode(', ', $taskQueue) : '', ]; diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 421f3c4..dca0012 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -41,7 +41,9 @@ public function serve(): void $hasDeclarations = false; foreach ($declarations as $declaration) { // Worker that listens on a task queue and hosts both workflow and activity implementations. - $taskQueues = $this->workerResolver->resolve($declaration->class); + $taskQueues = $declaration->taskQueue === null + ? $this->workerResolver->resolve($declaration->class) + : [$declaration->taskQueue]; foreach ($taskQueues as $taskQueue) { $worker = $registry->get($taskQueue); diff --git a/tests/src/Commands/InfoCommandTest.php b/tests/src/Commands/InfoCommandTest.php index 9d31edb..8cf1696 100644 --- a/tests/src/Commands/InfoCommandTest.php +++ b/tests/src/Commands/InfoCommandTest.php @@ -5,7 +5,9 @@ namespace Spiral\TemporalBridge\Tests\Commands; use Spiral\TemporalBridge\Attribute\AssignWorker; -use Spiral\TemporalBridge\DeclarationLocatorInterface; +use Spiral\TemporalBridge\Declaration\DeclarationDto; +use Spiral\TemporalBridge\Declaration\DeclarationType; +use Spiral\TemporalBridge\DeclarationRegistryInterface; use Spiral\TemporalBridge\Tests\TestCase; use Temporal\Activity\ActivityInterface; use Temporal\Activity\ActivityMethod; @@ -18,12 +20,24 @@ protected function setUp(): void { parent::setUp(); - $locator = $this->mockContainer(DeclarationLocatorInterface::class); - $locator->shouldReceive('getDeclarations')->andReturnUsing(function () { - yield WorkflowInterface::class => new \ReflectionClass(Workflow::class); - yield ActivityInterface::class => new \ReflectionClass(ActivityInterfaceWithWorker::class); - yield ActivityInterface::class => new \ReflectionClass(ActivityInterfaceWithoutWorker::class); - yield WorkflowInterface::class => new \ReflectionClass(AnotherWorkflow::class); + $locator = $this->mockContainer(DeclarationRegistryInterface::class); + $locator->shouldReceive('getDeclarationList')->andReturnUsing(function () { + yield new DeclarationDto( + type: DeclarationType::Workflow, + class: new \ReflectionClass(Workflow::class), + ); + yield new DeclarationDto( + type: DeclarationType::Activity, + class: new \ReflectionClass(ActivityInterfaceWithWorker::class), + ); + yield new DeclarationDto( + type: DeclarationType::Activity, + class: new \ReflectionClass(ActivityInterfaceWithoutWorker::class), + ); + yield new DeclarationDto( + type: DeclarationType::Workflow, + class: new \ReflectionClass(AnotherWorkflow::class), + ); }); }