Skip to content

Instantly share code, notes, and snippets.

@pwm
Last active July 2, 2018 15:03
Show Gist options
  • Save pwm/af496a960c07620ebd3daac14dafecb9 to your computer and use it in GitHub Desktop.
Save pwm/af496a960c07620ebd3daac14dafecb9 to your computer and use it in GitHub Desktop.
Reactive/Dependent FSMs
<?php
declare(strict_types=1);
interface Subject {
public function attach(Observer $observer): void;
}
interface Observer {
public function reactTo(string $event): void;
}
interface Stateful {
public function transitionState(string $event): string;
public function getState(): string;
}
class A implements Subject, Observer, Stateful
{
public const STATE_A1 = 'STATE_A1';
public const STATE_A2 = 'STATE_A2';
public const STATE_A3 = 'STATE_A3';
public const EVENT_A1 = 'EVENT_A1';
public const EVENT_A2 = 'EVENT_A2';
private const FSM = [
self::STATE_A1 => [self::EVENT_A1 => self::STATE_A2],
self::STATE_A2 => [self::EVENT_A2 => self::STATE_A3],
];
private const EVENT_MAP = [
B::EVENT_B1 => self::EVENT_A2,
];
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_A1;
/** @var array|Observer[] */
private $observers;
public function reactTo(string $event): void {
$nextEvent = self::EVENT_MAP[$event] ?? null;
if (is_string($nextEvent)) {
$this->transitionState($nextEvent);
}
}
public function attach(Observer $observer): void {
$this->observers[] = $observer;
}
public function transitionState(string $event): string {
$this->events[] = $event;
$this->state = self::FSM[$this->state][$event] ?? $this->state;
foreach ($this->observers as $observer) {
$observer->reactTo($event);
}
return $this->state;
}
public function getState(): string {
return $this->state;
}
}
class B implements Subject, Observer, Stateful
{
public const STATE_B1 = 'STATE_B1';
public const STATE_B2 = 'STATE_B2';
public const STATE_B3 = 'STATE_B3';
public const EVENT_B1 = 'EVENT_B1';
public const EVENT_B2 = 'EVENT_B2';
private const FSM = [
self::STATE_B1 => [self::EVENT_B1 => self::STATE_B2],
self::STATE_B2 => [self::EVENT_B2 => self::STATE_B3],
];
private const EVENT_MAP = [
A::EVENT_A1 => self::EVENT_B1,
A::EVENT_A2 => self::EVENT_B2,
];
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_B1;
/** @var array|Observer[] */
private $observers;
public function reactTo(string $event): void {
$nextEvent = self::EVENT_MAP[$event] ?? null;
if (is_string($nextEvent)) {
$this->transitionState($nextEvent);
}
}
public function attach(Observer $observer): void {
$this->observers[] = $observer;
}
public function transitionState(string $event): string {
$this->events[] = $event;
$this->state = self::FSM[$this->state][$event] ?? $this->state;
foreach ($this->observers as $observer) {
$observer->reactTo($event);
}
return $this->state;
}
public function getState(): string {
return $this->state;
}
}
$a = new A();
$b = new B();
$a->attach($b);
$b->attach($a);
// starts a chain reaction that eventually transitions both A and B states to B3
$a->transitionState(A::EVENT_A1);
assert($a->getState() === A::STATE_A3);
assert($b->getState() === B::STATE_B3);
<?php
declare(strict_types=1);
interface Actor
{
public function act(string $event, Conductor $conductor): string;
}
interface Stateful
{
public function getState(): string;
}
class A implements Actor, Stateful
{
public const STATE_A1 = 'STATE_A1';
public const STATE_A2 = 'STATE_A2';
public const STATE_A3 = 'STATE_A3';
public const EVENT_A1 = 'EVENT_A1';
public const EVENT_A2 = 'EVENT_A2';
private const FSM = [
self::STATE_A1 => [self::EVENT_A1 => self::STATE_A2],
self::STATE_A2 => [self::EVENT_A2 => self::STATE_A3],
];
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_A1;
public function act(string $event, Conductor $conductor): string
{
$this->events[] = $event;
$this->state = self::FSM[$this->state][$event] ?? $this->state;
$conductor->publish($this, $event);
return $this->state;
}
public function getState(): string
{
return $this->state;
}
}
class B implements Actor, Stateful
{
public const STATE_B1 = 'STATE_B1';
public const STATE_B2 = 'STATE_B2';
public const STATE_B3 = 'STATE_B3';
public const EVENT_B1 = 'EVENT_B1';
public const EVENT_B2 = 'EVENT_B2';
private const FSM = [
self::STATE_B1 => [self::EVENT_B1 => self::STATE_B2],
self::STATE_B2 => [self::EVENT_B2 => self::STATE_B3],
];
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_B1;
public function act(string $event, Conductor $conductor): string
{
$this->events[] = $event;
$this->state = self::FSM[$this->state][$event] ?? $this->state;
$conductor->publish($this, $event);
return $this->state;
}
public function getState(): string
{
return $this->state;
}
}
class Conductor
{
/** @var array */
private $reactionMap;
/** @var array|Actor[] */
private $actors;
public function __construct(array $reactionMap, Actor ...$actors)
{
$this->reactionMap = $reactionMap;
foreach ($actors as $actor) {
$this->actors[get_class($actor)] = $actor;
}
}
public function publish(Actor $actor, string $event): void
{
$key = get_class($actor);
if (isset($this->reactionMap[$key][$event])) {
[$reactor, $event] = $this->reactionMap[$key][$event];
$this->actors[$reactor]->act($event, $this);
}
}
}
$a = new A();
$b = new B();
$reactionMap = [
A::class => [
A::EVENT_A1 => [B::class, B::EVENT_B1],
A::EVENT_A2 => [B::class, B::EVENT_B2],
],
B::class => [
B::EVENT_B1 => [A::class, A::EVENT_A2],
],
];
$a->act(A::EVENT_A1, new Conductor($reactionMap, $a, $b));
assert($a->getState() === A::STATE_A3);
assert($b->getState() === B::STATE_B3);
<?php
declare(strict_types=1);
interface Actor
{
public function act(string $event): string;
}
interface Stateful
{
public function getState(): string;
}
class A implements Actor, Stateful
{
public const STATE_A1 = 'STATE_A1';
public const STATE_A2 = 'STATE_A2';
public const STATE_A3 = 'STATE_A3';
public const EVENT_A1 = 'EVENT_A1';
public const EVENT_A2 = 'EVENT_A2';
private const FSM = [
self::STATE_A1 => [self::EVENT_A1 => self::STATE_A2],
self::STATE_A2 => [self::EVENT_A2 => self::STATE_A3],
];
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_A1;
/** @var Conductor */
private $conductor;
public function __construct(Conductor $conductor)
{
$this->conductor = $conductor;
$conductor->addActor($this);
}
public function act(string $event): string
{
$this->events[] = $event;
$this->state = self::FSM[$this->state][$event] ?? $this->state;
$this->conductor->publish($this, $event);
return $this->state;
}
public function getState(): string
{
return $this->state;
}
}
class B implements Actor, Stateful
{
public const STATE_B1 = 'STATE_B1';
public const STATE_B2 = 'STATE_B2';
public const STATE_B3 = 'STATE_B3';
public const EVENT_B1 = 'EVENT_B1';
public const EVENT_B2 = 'EVENT_B2';
private const FSM = [
self::STATE_B1 => [self::EVENT_B1 => self::STATE_B2],
self::STATE_B2 => [self::EVENT_B2 => self::STATE_B3],
];
/** @var array|string[] */
private $events = [];
/** @var string */
private $state = self::STATE_B1;
/** @var Conductor */
private $conductor;
public function __construct(Conductor $conductor)
{
$this->conductor = $conductor;
$conductor->addActor($this);
}
public function act(string $event): string
{
$this->events[] = $event;
$this->state = self::FSM[$this->state][$event] ?? $this->state;
$this->conductor->publish($this, $event);
return $this->state;
}
public function getState(): string
{
return $this->state;
}
}
class Conductor
{
/** @var array */
private $reactionMap;
/** @var array|Actor[] */
private $actors;
public function __construct(array $reactionMap)
{
$this->reactionMap = $reactionMap;
}
public function addActor(Actor $actor): void
{
$this->actors[get_class($actor)] = $actor;
}
public function publish(Actor $actor, string $event): void
{
$key = get_class($actor);
if (isset($this->reactionMap[$key][$event])) {
[$reactor, $event] = $this->reactionMap[$key][$event];
$this->actors[$reactor]->act($event);
}
}
}
$reactionMap = [
A::class => [
A::EVENT_A1 => [B::class, B::EVENT_B1],
A::EVENT_A2 => [B::class, B::EVENT_B2],
],
B::class => [
B::EVENT_B1 => [A::class, A::EVENT_A2],
],
];
$conductor = new Conductor($reactionMap);
$a = new A($conductor);
$b = new B($conductor);
$a->act(A::EVENT_A1);
assert($a->getState() === A::STATE_A3);
assert($b->getState() === B::STATE_B3);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment