Passed
Push — master ( b67ee2...b8b5dc )
by Javier
03:09
created

StateMachine::fireEvent()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 38
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 5

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 22
nc 9
nop 1
dl 0
loc 38
ccs 22
cts 22
cp 1
crap 5
rs 9.2568
c 2
b 0
f 0
1
<?php
2
3
namespace jagarsoft\StateMachine;
4
5
use jagarsoft\StateMachine\Stubs\StateMachineBuilder;
6
7
class StateMachine {
8
    private const NEXT_STATE = 0;
9
    private const EXEC_ACTION = 1;
10
    
11
    protected $sm = [];
12
    protected $currentState = null;
13
    protected $currentEvent = null;
14
    protected $nextState = null;
15
16
    private $cancelTransition = false;
17
    private $transitionInProgress = false;
18
    private $eventsQueued = [];
19
20 26
    public function __construct(StateMachineBuilder $smb = null)
21
    {
22 26
        if(  $smb != null )
23 1
            $sm = $smb->__invoke();
24
25 26
        if( ! empty($sm) ){
26
            // StateEnum::CURRENT_STATE => [ EventEnum::ON_EVENT => [ StateEnum::NEXT_STATE_2, ActionClosureOrFunction ]  ],
27 1
            foreach ($sm as $state => $transition){
28 1
                $this->addState($state);
29 1
                foreach ($transition as $onEvent => $nextStateAndAction) {
30 1
                    if( array_key_exists(self::EXEC_ACTION, $nextStateAndAction) ) {
31
                        $this->addTransition($state, $onEvent, $nextStateAndAction[self::NEXT_STATE], $nextStateAndAction[self::EXEC_ACTION]);
32
                    } else {
33 1
                        $this->addTransition($state, $onEvent, $nextStateAndAction[self::NEXT_STATE]);
34
                    }
35
                }
36
            }
37
        }
38 26
    }
39
40 18
    public function addState($state)
41
    {
42 18
        $this->argumentIsValidOrFail($state);
43
44 16
        $this->setCurrentStateIfThisIsInitialState($state);
45
46 16
        $this->sm[$state] = [];
47
48 16
        return $this;
49
    }
50
51 18
    public function addTransition($currentState, $currentEvent, $nextState, \Closure $execAction = null )
52
    {
53 18
        $this->argumentIsValidOrFail($currentState);
54 18
        $this->argumentIsValidOrFail($currentEvent);
55 18
        $this->argumentIsValidOrFail($nextState);
56
57 18
        $this->setCurrentStateIfThisIsInitialState($currentState);
58
59 18
        $this->sm[$currentState][$currentEvent] = [
60 18
                                                self::NEXT_STATE => $nextState,
61 18
                                                self::EXEC_ACTION => $execAction
62
                                            ];
63 18
        return $this;
64
    }
65
66 4
    public function addCommonTransition($currentEvent, $nextState, \Closure $execAction = null )
67
    {
68 4
        $this->argumentIsValidOrFail($currentEvent);
69 4
        $this->argumentIsValidOrFail($nextState);
70
71 4
        $states = array_keys($this->sm);
72 4
        foreach ($states as $state) {
73 4
            $this->addTransition($state, $currentEvent, $nextState, $execAction);
74
        }
75
76 4
        return $this;
77
    }
78
79
    /**
80
     * @param $event
81
     */
82 15
    public function fireEvent($event)
83
    {
84 15
        $this->argumentIsValidOrFail($event);
85
86 15
        if( $this->transitionInProgress ){
87 2
            array_push($this->eventsQueued, $event);
88 2
            return $this;
89
        }
90 15
        $this->transitionInProgress = true;
91
92 15
        $this->eventMustExistOrFail($event);
93
94 14
        $transition = $this->sm[$this->currentState][$event];
95
96 14
        $this->nextState = $transition[self::NEXT_STATE];
97
98 14
        $this->stateMustExistOrFail($this->nextState);
99
100 13
        $this->currentEvent = $event;
101
102 13
        $action = $transition[self::EXEC_ACTION];
103 13
        if( $action ){
104 11
            ($action)($this);
105
        }
106
107 13
        if( $this->cancelTransition ){
108 5
            $this->cancelTransition = false;
109
        } else {
110 9
            $this->currentState = $transition[self::NEXT_STATE];
111
        }
112
113 13
        $this->transitionInProgress = false;
114 13
        $event = array_shift($this->eventsQueued);
115 13
        if(  $event != null ){
116 2
            $this->fireEvent($event);
117
        }
118
119 13
        return $this;
120
    }
121
122 5
    public function cancelTransition()
123
    {
124 5
        $this->cancelTransition = true;
125 5
    }
126
127 9
    public function getCurrentState()
128
    {
129 9
        return $this->currentState;
130
    }
131
132 4
    public function getCurrentEvent()
133
    {
134 4
        return $this->currentEvent;
135
    }
136
137 1
    public function getNextState()
138
    {
139 1
        return $this->nextState;
140
    }
141
142 1
    public function getMachineToArray()
143
    {
144 1
        return $this->sm;
145
    }
146
147 25
    private function argumentIsValidOrFail($arg): void
148
    {
149 25
        $this->argumentIsNotNullOrFail($arg);
150 24
        $this->argumentIsNotBlankOrFail($arg);
151 23
    }
152
153 25
    private function argumentIsNotNullOrFail($arg): void
154
    {
155 25
        if( $arg === null )
156 4
            throw new \InvalidArgumentException("Null is not an valid argument");
157 24
    }
158
159 24
    private function argumentIsNotBlankOrFail($arg): void
160
    {
161 24
        if( trim($arg) === "" )
162 2
            throw new \InvalidArgumentException("Blank is not an valid argument");
163 23
    }
164
165 15
    private function eventMustExistOrFail($event)
166
    {
167 15
        if( !( isset($this->sm[$this->currentState][$event]) ) )
168 1
            throw new \InvalidArgumentException("Unexpected event {$event} on {$this->currentState} state");
169 14
    }
170
171 14
    private function stateMustExistOrFail($state)
172
    {
173 14
        if( ! isset($this->sm[$state]) )
174 1
            throw new \InvalidArgumentException("Event '{$this->currentEvent}' fired an unexpected '{$state}' state");
175 13
    }
176
177 23
    private function setCurrentStateIfThisIsInitialState($state): void
178
    {
179 23
        if( $this->currentState == null){
180 23
            $this->currentState = $state;
181
        }
182 23
    }
183
}
184