StateMachine   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 171
Duplicated Lines 7.02 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 27
c 1
b 0
f 0
lcom 1
cbo 2
dl 12
loc 171
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A configure() 0 4 1
A fire() 0 18 4
A getCurrentState() 0 4 1
A isInState() 0 6 1
D toDotGraph() 12 37 10
A isSubState() 0 18 4
A getState() 0 8 2
A transition() 0 12 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace LightFsm;
4
5
class StateMachine 
6
{
7
    /** @var StateConfiguration */
8
    private $currentState;
9
10
    /** @var StateConfiguration[] */
11
    private $states = [];
12
13
    /** @var callable|null */
14
    private $changeCallback;
15
16
    /**
17
     * @param string|int|callable $initialState
18
     * @param callable|null       $changeCallback f($newState, $event, $oldState, $isSubState, $data)
19
     */
20
    public function __construct($initialState, $changeCallback = null)
21
    {
22
        $state = is_callable($initialState) ? call_user_func($initialState) : $initialState;
23
        $this->states[$state] = $this->currentState = new StateConfiguration($state);
24
        $this->changeCallback = $changeCallback;
25
    }
26
27
    /**
28
     * @param string|int $state
29
     *
30
     * @return StateConfiguration
31
     */
32
    public function configure($state)
33
    {
34
        return $this->getState($state);
35
    }
36
37
    /**
38
     * @param string|int $event
39
     * @param mixed      $data
40
     */
41
    public function fire($event, $data = null)
42
    {
43
        $transition = $this->currentState->getTransition($event);
44
        if (null === $transition) {
45
            return;
46
        }
47
48
        if ($transition->getGuardCallback()) {
49
            $ok = call_user_func($transition->getGuardCallback(), $data);
50
            if (!$ok) {
51
                return;
52
            }
53
        }
54
55
        $nextState = $this->getState($transition->getNextState());
56
57
        $this->transition($this->currentState, $event, $nextState, $data);
58
    }
59
60
    /**
61
     * @return int|string
62
     */
63
    public function getCurrentState()
64
    {
65
        return $this->currentState->getState();
66
    }
67
68
    /**
69
     * @param string|int $state
70
     *
71
     * @return bool
72
     */
73
    public function isInState($state)
74
    {
75
        $parentState = $this->getState($state);
76
77
        return $this->isSubState($this->currentState, $parentState);
78
    }
79
80
    public function toDotGraph()
81
    {
82
        $result = "digraph {\n";
83
        $listeners = '';
84
        foreach ($this->states as $state) {
85 View Code Duplication
            foreach ($state->getAllEntryCallbacks() as $name=>$callback) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
86
                if (is_int($name)) {
87
                    $name = 'listener';
88
                }
89
                $listeners .= sprintf("    \"%s\" -> \"%s\" [label=\"On Entry\"];\n", $state->getState(), $name);
90
            }
91 View Code Duplication
            foreach ($state->getAllExitCallbacks() as $name=>$callback) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
92
                if (is_int($name)) {
93
                    $name = 'listener';
94
                }
95
                $listeners .= sprintf("    \"%s\" -> \"%s\" [label=\"On Exit\"];\n", $state->getState(), $name);
96
            }
97
98
            foreach ($state->getAllTransitions() as $transition) {
99
                if ($transition->getGuardCallback()) {
100
                    $guardName = $transition->getGuardName() ?: 'condition';
101
                    $result .= sprintf("    \"%s\" -> \"%s\" [label=\"%s [%s]\"];\n", $state->getState(), $transition->getNextState(), $transition->getEvent(), $guardName);
102
                } else {
103
                    $result .= sprintf("    \"%s\" -> \"%s\" [label=\"%s\"];\n", $state->getState(), $transition->getNextState(), $transition->getEvent());
104
                }
105
            }
106
        }
107
108
        if ($listeners) {
109
            $result .= "    node [shape=box];\n";
110
            $result .= $listeners;
111
        }
112
113
        $result .= "}\n";
114
115
        return $result;
116
    }
117
118
    /**
119
     * @param StateConfiguration $child
120
     * @param StateConfiguration $parent
121
     *
122
     * @return bool
123
     */
124
    private function isSubState(StateConfiguration $child, StateConfiguration $parent)
125
    {
126
        $state = $child;
127
128
        while ($state) {
129
            if ($state->getState() === $parent->getState()) {
130
                return true;
131
            }
132
133
            if ($state->getParentState()) {
134
                $state = $this->getState($state->getParentState());
135
            } else {
136
                $state = null;
137
            }
138
        }
139
140
        return false;
141
    }
142
143
    /**
144
     * @param string|int $state
145
     *
146
     * @return StateConfiguration
147
     */
148
    private function getState($state)
149
    {
150
        if (false === isset($this->states[$state])) {
151
            $this->states[$state] = new StateConfiguration($state);
152
        }
153
154
        return $this->states[$state];
155
    }
156
157
    /**
158
     * @param StateConfiguration $previousState
159
     * @param string|int         $event
160
     * @param StateConfiguration $nextState
161
     * @param mixed              $data
162
     */
163
    private function transition(StateConfiguration $previousState, $event, StateConfiguration $nextState, $data)
164
    {
165
        $isSubState = $this->isSubState($this->currentState, $nextState);
166
167
        $previousState->triggerExit($isSubState, $data);
168
        $this->currentState = $nextState;
169
        $nextState->triggerEntry($isSubState, $data);
170
171
        if ($this->changeCallback) {
172
            call_user_func($this->changeCallback, $nextState->getState(), $event, $previousState->getState(), $isSubState, $data);
173
        }
174
    }
175
}
176