Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor DeclarationLocator #86

Merged
merged 2 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading