Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
6671ec3eac | |||
0bca797e8d | |||
474ccbb012 | |||
bf8e95561e | |||
9ab9561270 | |||
07f8ae467c |
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace NoccyLabs\React\CommandBus;
|
namespace NoccyLabs\React\CommandBus;
|
||||||
|
|
||||||
|
use ReflectionFunction;
|
||||||
|
use ReflectionNamedType;
|
||||||
use React\Promise\Deferred;
|
use React\Promise\Deferred;
|
||||||
use React\Promise\Promise;
|
use React\Promise\Promise;
|
||||||
use React\Promise\PromiseInterface;
|
use React\Promise\PromiseInterface;
|
||||||
@ -17,10 +19,13 @@ class Command
|
|||||||
/** @var callable $handler The handler */
|
/** @var callable $handler The handler */
|
||||||
private $handler;
|
private $handler;
|
||||||
|
|
||||||
public function __construct(string $name, callable $handler)
|
private ?array $signature;
|
||||||
|
|
||||||
|
public function __construct(string $name, callable $handler, ?array $signature = null)
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->handler = $handler;
|
$this->handler = $handler;
|
||||||
|
$this->signature = $signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(): string
|
public function getName(): string
|
||||||
@ -38,5 +43,36 @@ class Command
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function parameters(): array
|
||||||
|
{
|
||||||
|
$refl = new ReflectionFunction($this->handler);
|
||||||
|
$args = [];
|
||||||
|
|
||||||
|
foreach ($refl->getParameters() as $parameter) {
|
||||||
|
$name = $parameter->getName();
|
||||||
|
$type = null;
|
||||||
|
if (!$parameter->hasType()) {
|
||||||
|
$type = 'any';
|
||||||
|
} else {
|
||||||
|
$type = $parameter->getType();
|
||||||
|
if ($type instanceof ReflectionNamedType && $type->isBuiltin()) {
|
||||||
|
$type = ($type->allowsNull() ? "?" : "") . $type->getName();
|
||||||
|
} else {
|
||||||
|
$type = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($parameter->isDefaultValueAvailable()) $type = "{$type}=".\json_encode($parameter->getDefaultValue(),\JSON_UNESCAPED_SLASHES);
|
||||||
|
if ($type !== null) $args[$name] = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignature(): array
|
||||||
|
{
|
||||||
|
return $this->signature ?? $this->parameters();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,30 +13,97 @@ class CommandBus implements CommandBusInterface
|
|||||||
{
|
{
|
||||||
use EventEmitterTrait;
|
use EventEmitterTrait;
|
||||||
|
|
||||||
private CommandRegistry $commandRegistry;
|
/** @var array<CommandResolverInterface> */
|
||||||
|
private $resolvers = [];
|
||||||
|
|
||||||
private SplObjectStorage $connections;
|
private SplObjectStorage $connections;
|
||||||
|
|
||||||
private SplObjectStorage $servers;
|
private SplObjectStorage $servers;
|
||||||
|
|
||||||
public function __construct(CommandRegistry $commandRegistry)
|
public function __construct(?CommandResolverInterface $commands=null)
|
||||||
{
|
{
|
||||||
$this->commandRegistry = $commandRegistry;
|
if ($commands) {
|
||||||
|
$this->resolvers[] = $commands;
|
||||||
|
}
|
||||||
$this->connections = new SplObjectStorage();
|
$this->connections = new SplObjectStorage();
|
||||||
$this->servers = new SplObjectStorage();
|
$this->servers = new SplObjectStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRegistry(): CommandRegistry
|
/**
|
||||||
|
* Get the command registry
|
||||||
|
*
|
||||||
|
* @deprecated Use getCommandNames() instead
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getRegistry(): ?CommandRegistry
|
||||||
{
|
{
|
||||||
return $this->commandRegistry;
|
return null; // $this->commandRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a CommandResolver
|
||||||
|
*
|
||||||
|
* @param CommandResolverInterface $resolver
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addResolver(CommandResolverInterface $resolver): void
|
||||||
|
{
|
||||||
|
$this->resolvers[] = $resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeResolver(CommandResolverInterface $resolver): void
|
||||||
|
{
|
||||||
|
// FIXME implement
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the names of defined commands
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getCommandNames(): array
|
||||||
|
{
|
||||||
|
$commands = [];
|
||||||
|
foreach ($this->resolvers as $resolver) {
|
||||||
|
$commands = array_merge($commands, $resolver->getCommandNames());
|
||||||
|
}
|
||||||
|
sort($commands);
|
||||||
|
return array_unique($commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a command by searching through the resolvers
|
||||||
|
*
|
||||||
|
* @param string $command
|
||||||
|
* @return Command|null
|
||||||
|
*/
|
||||||
|
public function findCommand(string $command): ?Command
|
||||||
|
{
|
||||||
|
foreach ($this->resolvers as $resolver) {
|
||||||
|
if ($found = $resolver->findCommand($command))
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a server listener
|
||||||
|
*
|
||||||
|
* @param ServerInterface $server
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function addServer(ServerInterface $server): void
|
public function addServer(ServerInterface $server): void
|
||||||
{
|
{
|
||||||
$this->servers->attach($server);
|
$this->servers->attach($server);
|
||||||
$server->on('connection', $this->onServerConnection(...));
|
$server->on('connection', $this->onServerConnection(...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a server listener
|
||||||
|
*
|
||||||
|
* @param ServerInterface $server
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function removeServer(ServerInterface $server): void
|
public function removeServer(ServerInterface $server): void
|
||||||
{
|
{
|
||||||
$server->close();
|
$server->close();
|
||||||
@ -44,6 +111,11 @@ class CommandBus implements CommandBusInterface
|
|||||||
$this->servers->detach($server);
|
$this->servers->detach($server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close and shutdown the servers
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function close(): void
|
public function close(): void
|
||||||
{
|
{
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
@ -106,7 +178,7 @@ class CommandBus implements CommandBusInterface
|
|||||||
|
|
||||||
private function executeContext(Context $context): PromiseInterface
|
private function executeContext(Context $context): PromiseInterface
|
||||||
{
|
{
|
||||||
$command = $this->commandRegistry->find($context->getCommandName());
|
$command = $this->findCommand($context->getCommandName());
|
||||||
if (!$command) return new Promise(function (callable $resolve) use ($context) {
|
if (!$command) return new Promise(function (callable $resolve) use ($context) {
|
||||||
throw new \RuntimeException("Unable to resolve command: ".$context->getCommandName());
|
throw new \RuntimeException("Unable to resolve command: ".$context->getCommandName());
|
||||||
});
|
});
|
||||||
|
@ -38,8 +38,8 @@ class CommandBusClient implements CommandBusInterface
|
|||||||
{
|
{
|
||||||
$this->commandRegistry = $commandRegistry;
|
$this->commandRegistry = $commandRegistry;
|
||||||
if ($commandRegistry) {
|
if ($commandRegistry) {
|
||||||
$commandRegistry->on(CommandRegistry::EVENT_REGISTERED, $this->onCommandRegistered(...));
|
$this->commandRegistry->on(CommandRegistry::EVENT_REGISTERED, $this->onCommandRegistered(...));
|
||||||
$commandRegistry->on(CommandRegistry::EVENT_UNREGISTERED, $this->onCommandUnregistered(...));
|
$this->commandRegistry->on(CommandRegistry::EVENT_UNREGISTERED, $this->onCommandUnregistered(...));
|
||||||
}
|
}
|
||||||
$this->connector = $connector??new TcpConnector();
|
$this->connector = $connector??new TcpConnector();
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use Evenement\EventEmitterTrait;
|
|||||||
* A collection of commands that can be executed via CommandBusInterface
|
* A collection of commands that can be executed via CommandBusInterface
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class CommandRegistry implements EventEmitterInterface
|
class CommandRegistry implements CommandResolverInterface, EventEmitterInterface
|
||||||
{
|
{
|
||||||
use EventEmitterTrait;
|
use EventEmitterTrait;
|
||||||
|
|
||||||
@ -40,12 +40,12 @@ class CommandRegistry implements EventEmitterInterface
|
|||||||
$this->emit(self::EVENT_UNREGISTERED, [ $command ]);
|
$this->emit(self::EVENT_UNREGISTERED, [ $command ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function find(string $command): ?Command
|
public function findCommand(string $command): ?Command
|
||||||
{
|
{
|
||||||
return $this->commands[$command] ?? null;
|
return $this->commands[$command] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNames(): array
|
public function getCommandNames(): array
|
||||||
{
|
{
|
||||||
return array_keys($this->commands);
|
return array_keys($this->commands);
|
||||||
}
|
}
|
||||||
|
19
src/CommandResolverInterface.php
Normal file
19
src/CommandResolverInterface.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\React\CommandBus;
|
||||||
|
use Evenement\EventEmitterInterface;
|
||||||
|
use Evenement\EventEmitterTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of commands that can be executed via CommandBusInterface
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
interface CommandResolverInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
public function findCommand(string $command): ?Command;
|
||||||
|
|
||||||
|
public function getCommandNames(): array;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
52
src/CommandResolverTrait.php
Normal file
52
src/CommandResolverTrait.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\React\CommandBus;
|
||||||
|
use Evenement\EventEmitterInterface;
|
||||||
|
use Evenement\EventEmitterTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of commands that can be executed via CommandBusInterface
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
trait CommandResolverTrait
|
||||||
|
{
|
||||||
|
|
||||||
|
const EVENT_REGISTERED = 'registered';
|
||||||
|
const EVENT_UNREGISTERED = 'unregistered';
|
||||||
|
|
||||||
|
/** @var array<string,Command> */
|
||||||
|
private array $commands = [];
|
||||||
|
|
||||||
|
public function registerCommand(string $command, callable $handler): void
|
||||||
|
{
|
||||||
|
$isNew = !array_key_exists($command, $this->commands);
|
||||||
|
|
||||||
|
$this->commands[$command] = new Command($command, $handler);
|
||||||
|
|
||||||
|
if ($isNew) {
|
||||||
|
$this->emit(self::EVENT_REGISTERED, [ $command ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unregisterCommand(string $command): void
|
||||||
|
{
|
||||||
|
if (!array_key_exists($command, $this->commands)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->commands[$command]);
|
||||||
|
|
||||||
|
$this->emit(self::EVENT_UNREGISTERED, [ $command ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findCommand(string $command): ?Command
|
||||||
|
{
|
||||||
|
return $this->commands[$command] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommandNames(): array
|
||||||
|
{
|
||||||
|
return array_keys($this->commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,8 +48,8 @@ class CommandRegistryTest extends \PHPUnit\Framework\TestCase
|
|||||||
$reg->register('a', $cmda);
|
$reg->register('a', $cmda);
|
||||||
$reg->register('b', $cmdb);
|
$reg->register('b', $cmdb);
|
||||||
|
|
||||||
$this->assertEquals('a', $reg->find('a')?->getName());
|
$this->assertEquals('a', $reg->findCommand('a')?->getName());
|
||||||
$this->assertEquals('b', $reg->find('b')?->getName());
|
$this->assertEquals('b', $reg->findCommand('b')?->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGettingCommandNames()
|
public function testGettingCommandNames()
|
||||||
@ -61,7 +61,7 @@ class CommandRegistryTest extends \PHPUnit\Framework\TestCase
|
|||||||
$reg->register('a', $cmda);
|
$reg->register('a', $cmda);
|
||||||
$reg->register('b', $cmdb);
|
$reg->register('b', $cmdb);
|
||||||
|
|
||||||
$this->assertEquals(['a','b'], $reg->getNames());
|
$this->assertEquals(['a','b'], $reg->getCommandNames());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -26,4 +26,14 @@ class CommandTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this->assertEquals(true, $hit);
|
$this->assertEquals(true, $hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCommandReflection()
|
||||||
|
{
|
||||||
|
$command = new Command("test", function (string $a, ?int $b, bool $c = false) { });
|
||||||
|
$expect = [
|
||||||
|
'a' => 'string',
|
||||||
|
'b' => '?int',
|
||||||
|
'c' => 'bool=false'
|
||||||
|
];
|
||||||
|
$this->assertEquals($expect, $command->parameters());
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user