Last active
July 5, 2018 09:09
-
-
Save pwm/6895566f3c1a7277ca5a70e7b1763fa7 to your computer and use it in GitHub Desktop.
Actors
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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