Initial commit
This commit is contained in:
commit
befe5f5d59
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/vendor/
|
||||
/.phpunit.*
|
||||
/composer.lock
|
52
README.md
Normal file
52
README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Command Bus for ReactPHP
|
||||
|
||||
* Can run monolithic (create a bus and use as is), or distributed (create bus and use clients), or a hybrid.
|
||||
* All commands called asynchronously using promises and deferreds.
|
||||
|
||||
## Installing
|
||||
|
||||
```shell
|
||||
$ composer require noccylabs/react-command-bus:^0.1.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```php
|
||||
// This is enough to setup a local bus.
|
||||
$bus = new CommandBus();
|
||||
$bus->register('hello', function (Context $context) {
|
||||
return new Promise(function (callable $resolve) use ($context) {
|
||||
return $resolve([ 'message' => "Hello, {$context->name}" ]);
|
||||
});
|
||||
});
|
||||
|
||||
// You can call it as expected
|
||||
$bus->execute('hello', [ 'name' => "Bob" ])
|
||||
->then(function (array $result) {
|
||||
echo "Result: {$result['message']}\n";
|
||||
});
|
||||
|
||||
// Add a listener, and you can now connect to it!
|
||||
$bus->addServer($server);
|
||||
|
||||
|
||||
// So using this in another script works as expected, if you consider
|
||||
// the async flow. See the examples for working examples.
|
||||
|
||||
$client = new CommandBusClient();
|
||||
$client->connect($socket);
|
||||
|
||||
$client->execute('hello', [ 'name' => "Bob" ])
|
||||
->then(function (array $result) {
|
||||
echo "Result: {$result['message']}\n";
|
||||
});
|
||||
|
||||
|
||||
// The bus can also notify all clients about important events
|
||||
$bus->notify('updateCompleted', [ 'info' => [] ]);
|
||||
|
||||
// Listening on the bus or client will yield the event
|
||||
$bus->on('notify', function (string $event, array $data) {});
|
||||
$client->on('notify', function (string $event, array $data) {});
|
||||
|
||||
```
|
29
composer.json
Normal file
29
composer.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "noccylabs/react-command-bus",
|
||||
"description": "A command bus for ReactPHP applications",
|
||||
"type": "library",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"NoccyLabs\\React\\CommandBus\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "NoccyLabs",
|
||||
"email": "labs@noccy.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"react/event-loop": "^1.5",
|
||||
"react/promise": "^3.1",
|
||||
"react/socket": "^1.15",
|
||||
"react/stream": "^1.3",
|
||||
"react/promise-timer": "^1.10",
|
||||
"symfony/uid": "^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"phpstan/phpstan": "^1.10"
|
||||
}
|
||||
}
|
33
examples/local.php
Normal file
33
examples/local.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__."/../vendor/autoload.php";
|
||||
|
||||
use NoccyLabs\React\CommandBus\CommandBus;
|
||||
use NoccyLabs\React\CommandBus\CommandRegistry;
|
||||
use NoccyLabs\React\CommandBus\Context;
|
||||
use React\EventLoop\Loop;
|
||||
use React\Promise\Promise;
|
||||
|
||||
$commands = new CommandRegistry();
|
||||
$commands->register("hello", function (Context $context) {
|
||||
return new Promise(function (callable $resolve) use ($context) {
|
||||
Loop::addTimer(1, function () use ($context, $resolve) {
|
||||
$resolve("Hello, {$context->name}");
|
||||
});
|
||||
});
|
||||
});
|
||||
$commands->register("hello2", function (Context $context) {
|
||||
return "Hello2, {$context->name}";
|
||||
});
|
||||
|
||||
$bus = new CommandBus($commands);
|
||||
|
||||
$bus->execute('hello', ['name'=>'Bob'])
|
||||
->then(function ($result) {
|
||||
var_dump($result);
|
||||
});
|
||||
|
||||
$bus->execute('hello2', ['name'=>'Bob'])
|
||||
->then(function ($result) {
|
||||
var_dump($result);
|
||||
});
|
45
examples/server.php
Normal file
45
examples/server.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__."/../vendor/autoload.php";
|
||||
|
||||
use NoccyLabs\React\CommandBus\CommandBus;
|
||||
use NoccyLabs\React\CommandBus\CommandBusClient;
|
||||
use NoccyLabs\React\CommandBus\CommandRegistry;
|
||||
use NoccyLabs\React\CommandBus\Context;
|
||||
use React\Promise\Promise;
|
||||
use React\Socket\SocketServer;
|
||||
|
||||
$commands = new CommandRegistry();
|
||||
// Register some function to call. The name here is "hello", and it will
|
||||
// receive a Context holding the call context. Any passed data will be
|
||||
// available as properties on the Context object.
|
||||
$commands->register("hello", function (Context $context) {
|
||||
// You don't have to, but you should return a promise from your
|
||||
// commands.
|
||||
return new Promise(function (callable $resolve) use ($context) {
|
||||
$resolve("Hello, {$context->name}");
|
||||
});
|
||||
});
|
||||
|
||||
// Create the CommandBus and pass the CommandRegistry
|
||||
$bus = new CommandBus($commands);
|
||||
|
||||
$server = new SocketServer("tcp://127.0.0.1:9999");
|
||||
$bus->addServer($server);
|
||||
|
||||
// The server is sorted, now for the client!
|
||||
|
||||
$client = new CommandBusClient();
|
||||
$client->on(CommandBusClient::EVENT_CONNECTED,
|
||||
function () use ($client, $bus) {
|
||||
$client->execute('hello', ['name'=>'Bob'])
|
||||
->then(function ($result) use ($client,$bus) {
|
||||
// Result from the call
|
||||
var_dump($result);
|
||||
// Shut down after receiving the response
|
||||
$bus->close();
|
||||
$client->close();
|
||||
});
|
||||
}
|
||||
);
|
||||
$client->connect("tcp://127.0.0.1:9999");
|
12
phpstan.neon
Normal file
12
phpstan.neon
Normal file
@ -0,0 +1,12 @@
|
||||
parameters:
|
||||
level: 5
|
||||
|
||||
excludePaths:
|
||||
- doc
|
||||
- vendor
|
||||
- tests
|
||||
|
||||
# Paths to include in the analysis
|
||||
paths:
|
||||
- src
|
||||
|
40
src/Command.php
Normal file
40
src/Command.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Command
|
||||
{
|
||||
/** @var string $name The command name */
|
||||
private string $name;
|
||||
|
||||
/** @var callable $handler The handler */
|
||||
private $handler;
|
||||
|
||||
public function __construct(string $name, callable $handler)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function call(Context $context): PromiseInterface
|
||||
{
|
||||
return new Promise(function (callable $resolve) use ($context) {
|
||||
$resolve(call_user_func($this->handler, $context));
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
115
src/CommandBus.php
Normal file
115
src/CommandBus.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
use Evenement\EventEmitterTrait;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ServerInterface;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
use SplObjectStorage;
|
||||
|
||||
class CommandBus implements CommandBusInterface
|
||||
{
|
||||
use EventEmitterTrait;
|
||||
|
||||
private CommandRegistry $commandRegistry;
|
||||
|
||||
private SplObjectStorage $connections;
|
||||
|
||||
private SplObjectStorage $servers;
|
||||
|
||||
public function __construct(CommandRegistry $commandRegistry)
|
||||
{
|
||||
$this->commandRegistry = $commandRegistry;
|
||||
$this->connections = new SplObjectStorage();
|
||||
$this->servers = new SplObjectStorage();
|
||||
}
|
||||
|
||||
public function addServer(ServerInterface $server): void
|
||||
{
|
||||
$this->servers->attach($server);
|
||||
$server->on('connection', $this->onServerConnection(...));
|
||||
}
|
||||
|
||||
public function removeServer(ServerInterface $server): void
|
||||
{
|
||||
$server->close();
|
||||
$server->removeListener('connection', $this->onServerConnection(...));
|
||||
$this->servers->detach($server);
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
foreach ($this->servers as $server) {
|
||||
$this->removeServer($server);
|
||||
}
|
||||
}
|
||||
|
||||
private function onServerConnection(DuplexStreamInterface $client): void
|
||||
{
|
||||
$this->connections->attach($client);
|
||||
$client->on('data', function ($data) use ($client) {
|
||||
try {
|
||||
$message = Message::fromString($data);
|
||||
$this->onClientMessage($client, $message);
|
||||
} catch (MessageException $e) {
|
||||
$client->end('{"msg":"error","data":{"error":"Bad message format"}}');
|
||||
}
|
||||
});
|
||||
$client->on('close', function () use ($client) {
|
||||
$this->connections->detach($client);
|
||||
});
|
||||
}
|
||||
|
||||
private function onClientMessage(DuplexStreamInterface $client, Message $message): void
|
||||
{
|
||||
switch ($message->getType()) {
|
||||
case Message::MSGTYPE_EXECUTE: // Client call to execute command
|
||||
$data = $message->getData();
|
||||
$context = new Context($data['command'],$data['context']);
|
||||
$this->executeContext($context)->then(
|
||||
function ($result) use ($message, $client) {
|
||||
$client->write($message->asResult($result)->toJson()."\n");
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Message::MSGTYPE_RESULT: // Result from execution on client
|
||||
// TODO implement me
|
||||
break;
|
||||
case Message::MSGTYPE_REGISTRY: // command registry actions
|
||||
// TODO implement me
|
||||
break;
|
||||
default:
|
||||
$client->end('{"msg":"error","data":{"error":"Unexpected message type"}}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function execute(string $command, array $context): PromiseInterface
|
||||
{
|
||||
$context = new Context($command, $context);
|
||||
return $this->executeContext($context);
|
||||
}
|
||||
|
||||
private function executeContext(Context $context): PromiseInterface
|
||||
{
|
||||
$command = $this->commandRegistry->find($context->getCommandName());
|
||||
if (!$command) return new Promise(function (callable $resolve) use ($context) {
|
||||
throw new \RuntimeException("Unable to resolve command: ".$context->getCommandName());
|
||||
});
|
||||
|
||||
return $command->call($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function notify(string $event, array $data): void
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
150
src/CommandBusClient.php
Normal file
150
src/CommandBusClient.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
use Evenement\EventEmitterTrait;
|
||||
use React\EventLoop\Loop;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\TcpConnector;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
use Throwable;
|
||||
|
||||
class CommandBusClient implements CommandBusInterface
|
||||
{
|
||||
use EventEmitterTrait;
|
||||
|
||||
const EVENT_ERROR = 'error';
|
||||
const EVENT_CONNECTED = 'connected';
|
||||
const EVENT_DISCONNECTED = 'disconnected';
|
||||
|
||||
private ?CommandRegistry $commandRegistry = null;
|
||||
|
||||
private ConnectorInterface $connector;
|
||||
|
||||
private ?DuplexStreamInterface $connection = null;
|
||||
|
||||
/** @var array<string,Deferred> */
|
||||
private array $pending = [];
|
||||
|
||||
private string $address;
|
||||
|
||||
public function __construct(?CommandRegistry $commandRegistry = null, ?ConnectorInterface $connector = null)
|
||||
{
|
||||
$this->commandRegistry = $commandRegistry;
|
||||
if ($commandRegistry) {
|
||||
$commandRegistry->on(CommandRegistry::EVENT_REGISTERED, $this->onCommandRegistered(...));
|
||||
$commandRegistry->on(CommandRegistry::EVENT_UNREGISTERED, $this->onCommandUnregistered(...));
|
||||
}
|
||||
$this->connector = $connector??new TcpConnector();
|
||||
}
|
||||
|
||||
public function connect(string $address): void
|
||||
{
|
||||
$this->address = $address;
|
||||
$this->reconnect();
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$this->connection->close();
|
||||
$this->connection->removeAllListeners();
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
private function reconnect(): void
|
||||
{
|
||||
if ($this->connection) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
$this->connector->connect($this->address)->then(
|
||||
function (DuplexStreamInterface $connection) {
|
||||
$this->connection = $connection;
|
||||
$this->emit(self::EVENT_CONNECTED);
|
||||
$connection->on('close', function () {
|
||||
$this->emit(self::EVENT_DISCONNECTED);
|
||||
});
|
||||
$connection->on('data', function ($data) use ($connection) {
|
||||
try {
|
||||
$message = Message::fromString($data);
|
||||
$this->onServerMessage($message);
|
||||
} catch (MessageException $e) {
|
||||
$connection->end('{"msg":"error","data":{"error":"Bad message format"}}');
|
||||
}
|
||||
});
|
||||
},
|
||||
function (Throwable $e) {
|
||||
$this->emit(self::EVENT_ERROR, [ $e->getMessage(), $e ]);
|
||||
Loop::addTimer(5, $this->reconnect(...));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function onServerMessage(Message $message): void
|
||||
{
|
||||
// fprintf(STDERR, "onServerMessage: %s\n", $message->toJson());
|
||||
switch ($message->getType()) {
|
||||
case Message::MSGTYPE_EXECUTE: // server call to execute command
|
||||
// TODO implement me
|
||||
break;
|
||||
case Message::MSGTYPE_RESULT: // result from server
|
||||
$uuid = $message->getUuid();
|
||||
$result = $message->getData()['result'];
|
||||
if (array_key_exists($uuid, $this->pending)) {
|
||||
$this->pending[$uuid]->resolve($result);
|
||||
unset($this->pending[$uuid]);
|
||||
}
|
||||
break;
|
||||
case Message::MSGTYPE_REGISTRY: // command registry actions
|
||||
// TODO implement me
|
||||
break;
|
||||
default:
|
||||
$this->connection->end('{"msg":"error","data":{"error":"Unexpected message type"}}');
|
||||
}
|
||||
}
|
||||
|
||||
private function onCommandRegistered(string $command): void
|
||||
{
|
||||
if ($this->connection && $this->connection->isWritable()) {
|
||||
$msg = new Message(Message::MSGTYPE_REGISTRY, [ 'register' => [ $command ]]);
|
||||
$this->connection->write($msg->toJson());
|
||||
}
|
||||
}
|
||||
|
||||
private function onCommandUnregistered(string $command): void
|
||||
{
|
||||
if ($this->connection && $this->connection->isWritable()) {
|
||||
$msg = new Message(Message::MSGTYPE_REGISTRY, [ 'unregister' => [ $command ]]);
|
||||
$this->connection->write($msg->toJson());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function execute(string $command, array $context): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$message = new Message(Message::MSGTYPE_EXECUTE, [
|
||||
'command' => $command,
|
||||
'context' => $context
|
||||
]);
|
||||
$this->pending[$message->getUuid()] = $deferred;
|
||||
|
||||
// fprintf(STDERR, "write: %s\n", $message->toJson());
|
||||
$this->connection->write($message->toJson()."\n");
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function notify(string $event, array $data): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
8
src/CommandBusException.php
Normal file
8
src/CommandBusException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
|
||||
interface CommandBusException
|
||||
{
|
||||
}
|
||||
|
33
src/CommandBusInterface.php
Normal file
33
src/CommandBusInterface.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
use Evenement\EventEmitterInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
interface CommandBusInterface extends EventEmitterInterface
|
||||
{
|
||||
/** @var string Emitted to distribrute notifications */
|
||||
const EVENT_NOTIFY = "notify";
|
||||
|
||||
/**
|
||||
* Execute a command on the command bus, and return a promise that will be
|
||||
* resolved once the command has been handled.
|
||||
*
|
||||
* @param string $command The name of the command to execute
|
||||
* @param array $context Data to pass in the call context
|
||||
* @®eturn PromiseInterface A promise that will be resolved with the result
|
||||
*/
|
||||
public function execute(string $command, array $context): PromiseInterface;
|
||||
|
||||
/**
|
||||
* Notify all connected clients to emit the notify event, and also be
|
||||
* emitted locally. These messages are unsolicited, and can not be
|
||||
* responded to. They are fire-and-forget.
|
||||
*
|
||||
* @param string $event The application-specific event name
|
||||
* @param array $data Event-specific data
|
||||
* @return void
|
||||
*/
|
||||
public function notify(string $event, array $data): void;
|
||||
}
|
||||
|
53
src/CommandRegistry.php
Normal file
53
src/CommandRegistry.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
use Evenement\EventEmitterInterface;
|
||||
use Evenement\EventEmitterTrait;
|
||||
|
||||
/**
|
||||
* A collection of commands that can be executed via CommandBusInterface
|
||||
*
|
||||
*/
|
||||
class CommandRegistry implements EventEmitterInterface
|
||||
{
|
||||
use EventEmitterTrait;
|
||||
|
||||
const EVENT_REGISTERED = 'registered';
|
||||
const EVENT_UNREGISTERED = 'unregistered';
|
||||
|
||||
/** @var array<string,Command> */
|
||||
private array $commands = [];
|
||||
|
||||
public function register(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 unregister(string $command): void
|
||||
{
|
||||
if (!array_key_exists($command, $this->commands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->commands[$command]);
|
||||
|
||||
$this->emit(self::EVENT_UNREGISTERED, [ $command ]);
|
||||
}
|
||||
|
||||
public function find(string $command): ?Command
|
||||
{
|
||||
return $this->commands[$command] ?? null;
|
||||
}
|
||||
|
||||
public function getNames(): array
|
||||
{
|
||||
return array_keys($this->commands);
|
||||
}
|
||||
}
|
||||
|
41
src/Context.php
Normal file
41
src/Context.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
|
||||
|
||||
/**
|
||||
* The context contains all the data relating to calling a command. It has an
|
||||
* unique identifier, the command to call, and the payload data.
|
||||
*
|
||||
*/
|
||||
class Context
|
||||
{
|
||||
|
||||
/** @var string $commandName The command name to call */
|
||||
private string $commandName;
|
||||
|
||||
/** @var array<string,mixed> The payload data */
|
||||
private array $payload = [];
|
||||
|
||||
public function __construct(string $commandName, array $payload)
|
||||
{
|
||||
$this->commandName = $commandName;
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
public function getCommandName(): string
|
||||
{
|
||||
return $this->commandName;
|
||||
}
|
||||
|
||||
public function getPayload(): array
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->payload[$name] ?? null;
|
||||
}
|
||||
}
|
||||
|
88
src/Message.php
Normal file
88
src/Message.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
|
||||
use JsonSerializable;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* A serializable message frame
|
||||
*
|
||||
*/
|
||||
class Message implements JsonSerializable
|
||||
{
|
||||
/** @var string Execute request */
|
||||
const MSGTYPE_EXECUTE = 'execute';
|
||||
/** @var string Execute result */
|
||||
const MSGTYPE_RESULT = 'result';
|
||||
/** @var string Notify event */
|
||||
const MSGTYPE_NOTIFY = 'notify';
|
||||
/** @var string Registry update (command list set and update) */
|
||||
const MSGTYPE_REGISTRY = 'registry';
|
||||
|
||||
/** @var string $uuid The message identifier */
|
||||
private string $uuid;
|
||||
|
||||
private string $messageType;
|
||||
|
||||
private array $messageData;
|
||||
|
||||
public function __construct(string $messageType, array $messageData = [], ?string $uuid = null)
|
||||
{
|
||||
$this->uuid = ($uuid && Uuid::isValid($uuid)) ? $uuid : (string)Uuid::v7();
|
||||
$this->messageType = $messageType;
|
||||
$this->messageData = $messageData;
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->messageType;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->messageData;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $data The JSON-encoded message data
|
||||
* @return Message
|
||||
* @throws MessageException if the message can not be parsed
|
||||
*/
|
||||
public static function fromString(string $data): Message
|
||||
{
|
||||
$json = @json_decode($data, true);
|
||||
if (!$json || empty($json['msg'])) {
|
||||
throw new MessageException("Invalid data");
|
||||
}
|
||||
return new Message($json['msg'], $json['data'], $json['uuid']);
|
||||
}
|
||||
|
||||
public function asResult($result): Message
|
||||
{
|
||||
return new Message(self::MSGTYPE_RESULT, [
|
||||
'result' => $result
|
||||
], $this->uuid);
|
||||
}
|
||||
|
||||
public function toJson(): string
|
||||
{
|
||||
return json_encode($this, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $this->uuid,
|
||||
'msg' => $this->messageType,
|
||||
'data' => $this->messageData
|
||||
];
|
||||
}
|
||||
}
|
||||
|
9
src/MessageException.php
Normal file
9
src/MessageException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\React\CommandBus;
|
||||
use RuntimeException;
|
||||
|
||||
class MessageException extends RuntimeException implements CommandBusException
|
||||
{
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user