Skip to content

Instantly share code, notes, and snippets.

@victorjonsson
Last active November 11, 2016 15:22
Show Gist options
  • Save victorjonsson/c9a89574d0d6bcad1ea5d1b093d16d4b to your computer and use it in GitHub Desktop.
Save victorjonsson/c9a89574d0d6bcad1ea5d1b093d16d4b to your computer and use it in GitHub Desktop.
fsm.php
<?php
interface State {
public function getState();
}
abstract class StateAbstract implements State {
const STATE_INIT = 'STATE_INIT';
protected $currentState = self::STATE_INIT;
public function getState()
{
return $this->currentState;
}
protected function transitionToNewState($newState)
{
$this->guardAgainsInvalidStateTransition($newState);
call_user_func_array([$this, 'enterNewState'], func_get_args());
}
private function guardAgainstInvalidStateTransition()
{
$validTransition = null;
foreach (static::$stateTransitions as $transition) {
if ($transition['to'] == $newState) {
$validTransition = in_array($this->currentState, $transition['from']);
break;
}
}
if ($validTransition === null) {
throw new UnknownStateException('Unknown state');
} elseif (!$validTransition) {
throw new InvalidStateTransationException(static::class, $this->currentState, $newState);
}
}
private function enterNewState($newState)
{
$callback = $this->getStateTransitionCallback($newState);
if (is_callable($callback)) {
call_user_func_array($callback, func_get_args());
$this->currentState = $newState;
}
}
/**
* Use this to execute a function before the new state is set
* @param string $newState
* @return callable|null
*/
protected function getStateTransitionCallback($newState)
{
return null;
}
}
trait StateTrait extends AbstractState {}
class SomeEntityHavingStateTransitions extends StateAbstract
{
const STATE_A = '';
const STATE_B = '';
const STATE_C = '';
protected static $stateTransitions = [
['to' => self::STATE_B, 'from' => [self::STATE_A]],
['to' => self::STATE_C, 'from' => [self::STATE_B]],
];
protected function getStateTransitionCallback($newState) {
switch ($newState) {
case self::STATE_A:
return function () {
};
break;
// and so on...
}
}
public function setState($newState) {
$this->transitionToNewState($newState);
}
}
class SomeEntity extends StateAbstract {
const STATE_IN_PROGRESS = 'IN_PROGRESS';
const STATE_SUCCESSFUL = 'SUCCESS';
const STATE_FAILED = 'FAILED';
protected static $stateTransitions = [
['to' => self::STATE_IN_PROGRESS, 'from' => [self::STATE_FAILED, self::STATE_INIT]],
['to' => self::STATE_FAILED, 'from' => [self::STATE_IN_PROGRESS]]
['to' => self::STATE_SUCCESSFUL, 'from' => [self::STATE_IN_PROGRESS]],
];
public function transitionToInProgressState(DateTime $date, $data)
{
$this->transitionToNewState(self::STATE_IN_PROGRESS);
$this->startedDate = $date;
$this->data = $data;
}
public function transitionToFailedState(DateTime $date)
{
$this->transitionToNewState(self::STATE_FAILED);
$this->failedDate = $date;
}
public function transitionToSuccessfulState(DateTime $date, $data)
{
$this->transitionToNewState(self::STATE_SUCCESSFUL);
$this->sucessfullDate = $date;
}
}
class DataProcessor {
function proccess(SomeEntity $data)
{
switch ($data->getState()) {
case SomeEntity::INIT_STATE:
$data->transitionToInProgressState(new DateTime(), $this->fetchData());
$this->doProcess($data);
break;
case SomeEntity::STATE_FAILED:
$data->transitionToInProgressState(new DateTime(), $data->getData());
$this->doProcess($data);
break;
default:
throw new UnexpectedValueException(
'Can not process data when in state "'.$data->getState().'"'
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment