Skip to content

Instantly share code, notes, and snippets.

@pwm
Last active July 5, 2018 09:09
Show Gist options
  • Save pwm/6895566f3c1a7277ca5a70e7b1763fa7 to your computer and use it in GitHub Desktop.
Save pwm/6895566f3c1a7277ca5a70e7b1763fa7 to your computer and use it in GitHub Desktop.
Actors
<?php
declare(strict_types=1);
namespace RFSM3;
use Pwm\SFlow\FSM;
use Pwm\SFlow\Transition;
require_once __DIR__ . '/../vendor/autoload.php';
class StatefulA
{
public const EVENT_A1 = 'EVENT_A1';
public const EVENT_A2 = 'EVENT_A2';
public const STATE_A1 = 'STATE_A1';
public const STATE_A2 = 'STATE_A2';
public const STATE_A3 = 'STATE_A3';
public const STATE_A4 = 'STATE_A4';
private const STATES = [
self::STATE_A1,
self::STATE_A2,
self::STATE_A3,
self::STATE_A4,
];
/** @var StatefulB */
private $b;
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_A1;
/** @var FSM */
private $fsm;
public function __construct(StatefulB $b)
{
$this->b = $b;
$this->fsm = $this->setupFSM();
}
public function getB(): StatefulB
{
return $this->b;
}
public function transitionState(string $event): void
{
$this->state = $this->deriveState($this->state, $event);
$this->events[] = $event;
}
public function getEvents(): array
{
return $this->events;
}
public function getState(): string
{
return $this->state;
}
private function setupFSM(): FSM
{
$transitions = [
(new Transition(self::EVENT_A1))->from(self::STATE_A1)->to(self::STATE_A2),
(new Transition(self::EVENT_A2))->from(self::STATE_A1)->to(self::STATE_A4),
(new Transition(self::EVENT_A2))->from(self::STATE_A2)->to(self::STATE_A3),
];
return reduce(function (FSM $fsm, Transition $transition): FSM {
return $fsm->addTransition($transition);
}, new FSM(self::STATES), $transitions);
}
private function deriveState(string $currentState, string $event): string
{
$stateOp = $this->fsm->deriveState($currentState, [$event]);
return $stateOp->isSuccess()
? $stateOp->getState()
: $currentState;
}
}
class StatefulB
{
public const EVENT_B1 = 'EVENT_B1';
public const STATE_B1 = 'STATE_B1';
public const STATE_B2 = 'STATE_B2';
private const STATES = [
self::STATE_B1,
self::STATE_B2,
];
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_B1;
/** @var FSM */
private $fsm;
public function __construct()
{
$this->fsm = $this->setupFSM();
}
public function transitionState(string $event): void
{
$this->state = $this->deriveState($this->state, $event);
$this->events[] = $event;
}
public function getEvents(): array
{
return $this->events;
}
public function getState(): string
{
return $this->state;
}
private function setupFSM(): FSM
{
$transitions = [
(new Transition(self::EVENT_B1))->from(self::STATE_B1)->to(self::STATE_B2),
];
return reduce(function (FSM $fsm, Transition $transition): FSM {
return $fsm->addTransition($transition);
}, new FSM(self::STATES), $transitions);
}
private function deriveState(string $currentState, string $event): string
{
$stateOp = $this->fsm->deriveState($currentState, [$event]);
return $stateOp->isSuccess()
? $stateOp->getState()
: $currentState;
}
}
// ====
interface Actor
{
public function connect(Actor $actor): void;
public function notify(string $event): void;
public function getWrapped();
}
class ActorA implements Actor
{
/** @var StatefulA */
private $a;
/** @var Actor */
private $conductor;
public function __construct(StatefulA $a)
{
$this->a = $a;
}
public function connect(Actor $actor): void
{
$this->conductor = $actor;
}
public function notify(string $event): void
{
$this->a->transitionState($event);
$this->conductor->notify($event);
}
public function getWrapped(): StatefulA
{
return $this->a;
}
}
class ActorB implements Actor
{
/** @var StatefulB */
private $b;
/** @var Actor */
private $conductor;
public function __construct(StatefulB $b)
{
$this->b = $b;
}
public function connect(Actor $actor): void
{
$this->conductor = $actor;
}
public function notify(string $event): void
{
$this->b->transitionState($event);
$this->conductor->notify($event);
}
public function getWrapped(): StatefulB
{
return $this->b;
}
}
class Conductor implements Actor
{
public const REACTOR_MAP = [
StatefulA::EVENT_A1 => [
[StatefulB::class, StatefulB::EVENT_B1],
],
StatefulB::EVENT_B1 => [
[StatefulA::class, StatefulA::EVENT_A2],
],
];
/** @var array|Actor[][] */
private $actors;
public function connect(Actor $actor): void
{
$actorType = self::getWrappedType($actor);
$actorHash = spl_object_hash($actor);
if (! isset($this->actors[$actorType][$actorHash])) {
$this->actors[$actorType][$actorHash] = $actor;
$actor->connect($this);
}
}
public function notify(string $action): void
{
if (isset(self::REACTOR_MAP[$action])) {
foreach (self::REACTOR_MAP[$action] as [$reactorType, $reaction]) {
foreach ($this->actors[$reactorType] as $reactor) {
$reactor->notify($reaction);
}
}
}
}
public function getWrapped(): self
{
return $this;
}
private static function getWrappedType(Actor $actor): string
{
return \get_class($actor->getWrapped());
}
}
// ====
$b = new StatefulB();
$a1 = new StatefulA($b);
$a2 = new StatefulA($b);
$a3 = new StatefulA($b);
$ab = new ActorB($b);
$aa1 = new ActorA($a1);
$aa2 = new ActorA($a2);
$aa3 = new ActorA($a3);
$conductor = new Conductor();
$conductor->connect($ab);
$conductor->connect($aa1);
$conductor->connect($aa2);
$conductor->connect($aa3);
$aa1->notify(StatefulA::EVENT_A1);
\assert($ab->getWrapped()->getState() === StatefulB::STATE_B2);
\assert($aa1->getWrapped()->getState() === StatefulA::STATE_A3);
\assert($aa2->getWrapped()->getState() === StatefulA::STATE_A4);
\assert($aa3->getWrapped()->getState() === StatefulA::STATE_A4);
\assert($ab->getWrapped()->getEvents() === [StatefulB::EVENT_B1]);
\assert($aa1->getWrapped()->getEvents() === [StatefulA::EVENT_A1, StatefulA::EVENT_A2]);
\assert($aa2->getWrapped()->getEvents() === [StatefulA::EVENT_A2]);
\assert($aa3->getWrapped()->getEvents() === [StatefulA::EVENT_A2]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment