Skip to content

Commit

Permalink
Merge pull request #86 from spiral/update-declaration-registry
Browse files Browse the repository at this point in the history
Refactor DeclarationLocator
  • Loading branch information
butschster committed May 20, 2024
2 parents 6a2fa0e + 2f5d4bb commit 908683b
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 54 deletions.
6 changes: 3 additions & 3 deletions src/Bootloader/TemporalBridgeBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 => static fn (): DeclarationLocator => new DeclarationLocator(
reader: new AttributeReader(),
),
DeclarationLocatorInterface::class => DeclarationLocator::class,
DeclarationRegistryInterface::class => DeclarationLocator::class,

WorkflowClientInterface::class => static fn(
TemporalConfig $config,
Expand Down
28 changes: 15 additions & 13 deletions src/Commands/InfoCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand All @@ -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) : '',
];

Expand Down
15 changes: 15 additions & 0 deletions src/Declaration/DeclarationDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Spiral\TemporalBridge\Declaration;

final class DeclarationDto
{
public function __construct(
public readonly DeclarationType $type,
public readonly \ReflectionClass $class,
public readonly ?string $taskQueue = null,
) {
}
}
11 changes: 11 additions & 0 deletions src/Declaration/DeclarationType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Spiral\TemporalBridge\Declaration;

enum DeclarationType: string
{
case Workflow = 'workflow';
case Activity = 'activity';
}
51 changes: 42 additions & 9 deletions src/DeclarationLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Spiral\Attributes\ReaderInterface;
use Spiral\Core\Attribute\Singleton;
use Spiral\TemporalBridge\Declaration\DeclarationDto;
use Spiral\TemporalBridge\Declaration\DeclarationType;
use Spiral\Tokenizer\Attribute\TargetAttribute;
use Spiral\Tokenizer\TokenizationListenerInterface;
use Temporal\Activity\ActivityInterface;
Expand All @@ -14,26 +16,41 @@
#[Singleton]
#[TargetAttribute(WorkflowInterface::class)]
#[TargetAttribute(ActivityInterface::class)]
final class DeclarationLocator implements DeclarationRegistryInterface, TokenizationListenerInterface
final class DeclarationLocator implements
DeclarationRegistryInterface,
TokenizationListenerInterface,
DeclarationLocatorInterface
{
/** @var list<DeclarationDto> */
private array $declarations = [];

public function __construct(
private readonly ReaderInterface $reader,
) {
}

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;
}
}

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/DeclarationLocatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface DeclarationLocatorInterface
* List of all declarations for workflows and activities.
*
* @return iterable<class-string<WorkflowInterface>|class-string<ActivityInterface>, \ReflectionClass>
* @deprecated Use {@see DeclarationRegistryInterface::getDeclarationList} instead.
*/
public function getDeclarations(): iterable;
}
13 changes: 11 additions & 2 deletions src/DeclarationRegistryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeclarationDto>
*/
public function getDeclarationList(): iterable;
}
24 changes: 12 additions & 12 deletions src/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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<WorkflowInterface>|class-string<ActivityInterface>, ReflectionClass> $declarations
*/
$declarations = $this->container->get(DeclarationRegistryInterface::class)->getDeclarations();
/** @var list<DeclarationDto> $declarations */
$declarations = $this->container->get(DeclarationRegistryInterface::class)->getDeclarationList();

// factory initiates and runs task queue specific activity and workflow workers
/** @var WorkerFactoryInterface $factory */
Expand All @@ -41,22 +39,24 @@ 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 = $declaration->taskQueue === null
? $this->workerResolver->resolve($declaration->class)
: [$declaration->taskQueue];

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()),
);
}
Expand Down
28 changes: 21 additions & 7 deletions tests/src/Commands/InfoCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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),
);
});
}

Expand Down
42 changes: 42 additions & 0 deletions tests/src/DeclarationLocatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 908683b

Please sign in to comment.