Test Failed
Push — master ( bf7c2b...532ba1 )
by Javier
03:33
created

StateMachine::getMachineToArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace jagarsoft\StateMachine;
4
5
use jagarsoft\StateMachine\StateMachineBuilder;
6
use phpDocumentor\Reflection\Types\Array_;
7
8
class StateMachine {
9
    protected $sm = [];
10
    protected $currentState = null;
11
    protected $currentEvent = null;
12
    protected $nextState = null;
13
14
    protected $cancelTransition = false;
15
    protected $transitionInProgress = false;
16
    protected $eventsQueued = [];
17
18
    public const NEXT_STATE = 0;
19
    public const EXEC_ACTION = 'EXEC_ACTION';
20 27
    public const EXEC_GUARD = 'EXEC_GUARD';
21
    public const EXEC_BEFORE = 'EXEC_BEFORE';
22 27
    public const EXEC_AFTER = 'EXEC_AFTER';
23 1
24
    public function __construct(StateMachineBuilder $smb = null)
25 27
    {
26
        if(  $smb != null )
27 1
            $this->smb = $smb->from();
0 ignored issues
show
Bug Best Practice introduced by
The property smb does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
28 1
29 1
        if( ! empty($this->smb) ){
30 1
            // StateEnum::CURRENT_STATE => [ EventEnum::ON_EVENT => [ NEXT_STATE, ActionClosureOrFunction ] ],
31
            foreach ($this->smb as $state => $transition){
32
                $this->addState($state);
33 1
                foreach ($transition as $onEvent => $nextStateAndAction) {
34
                    $this->addTransition($state, $onEvent,
35
                        $nextStateAndAction[self::NEXT_STATE],
36
                        $nextStateAndAction);
37
                }
38 27
            }
39
        }
40 19
    }
41
42 19
	public function to(){
43
        if( $this->smb == null )
44 17
            return;
45
46 17
        $this->smb->to($this->getMachineToArray());
47
    }
48 17
49
    public function addState($state)
50
    {
51 19
        $this->argumentIsValidOrFail($state);
52
53 19
        $this->setCurrentStateIfThisIsInitialState($state);
54 19
55 19
        $this->sm[$state] = [];
56
57 19
        return $this;
58
    }
59 19
60 19
    public function addTransition($currentState, $currentEvent, $nextState,
61 19
                                  /*\Closure|array*/ $execAction = null,
62
                                  \Closure $execGuard = null,
63 19
                                  \Closure $execBefore = null,
64
                                  \Closure $execAfter = null)
65
    {
66 4
        $this->argumentIsValidOrFail($currentState);
67
        $this->argumentIsValidOrFail($currentEvent);
68 4
        $this->argumentIsValidOrFail($nextState);
69 4
70
        $this->setCurrentStateIfThisIsInitialState($currentState);
71 4
72 4
        if( is_array($execAction) ){
73 4
            $this->sm[$currentState][$currentEvent] = [ self::NEXT_STATE => $nextState ];
74
            $arrayActions = $execAction;
75
            foreach ($arrayActions as $key => $value) {
76 4
                $this->sm[$currentState][$currentEvent][$key] = $value;
77
            }
78
        } else {
79
            $this->sm[$currentState][$currentEvent] = [
80
                self::NEXT_STATE => $nextState,
81
                self::EXEC_ACTION => $execAction,
82 15
                self::EXEC_GUARD => $execGuard,
83
                self::EXEC_BEFORE => $execBefore,
84 15
                self::EXEC_AFTER => $execAfter,
85
            ];
86 15
        }
87 2
        return $this;
88 2
    }
89
90 15
    public function addCommonTransition($currentEvent, $nextState,
91
                                        \Closure $execAction = null,
92 15
                                        \Closure $execGuard = null,
93
                                        \Closure $execBefore = null,
94 14
                                        \Closure $execAfter = null)
95
    {
96 14
        $this->argumentIsValidOrFail($currentEvent);
97
        $this->argumentIsValidOrFail($nextState);
98 14
99
        $states = array_keys($this->sm);
100 13
        foreach ($states as $state) {
101
            $this->addTransition($state, $currentEvent, $nextState,
102 13
                                    $execAction, $execGuard, $execBefore, $execAfter);
103 13
        }
104 11
105
        return $this;
106
    }
107 13
108 5
    /**
109
     * @param $event
110 9
     * @noinspection PhpArrayPushWithOneElementInspection
111
     */
112
    public function fireEvent($event)
113 13
    {
114 13
        $this->argumentIsValidOrFail($event);
115 13
116 2
        if ($this->transitionInProgress) {
117
            array_push($this->eventsQueued, $event);
118
            return $this;
119 13
        }
120
        $this->transitionInProgress = true;
121
122 1
        $this->eventMustExistOrFail($event);
123
124
        $transition = $this->sm[$this->currentState][$event];
125 1
126 1
        $this->nextState = $transition[self::NEXT_STATE];
127 1
128 1
        $this->stateMustExistOrFail($this->nextState);
129
130 1
        $this->currentEvent = $event;
131
132
        $wasGuarded = false;
133 5
        if( array_key_exists(self::EXEC_GUARD, $transition) ){
134
            $guard = $transition[self::EXEC_GUARD];
135 5
            if ($guard) {
136 5
                if (($guard)($this) === false)
137
                    $wasGuarded = true;
138 9
            }
139
        }
140 9
        if ( ! $wasGuarded) {
141
            if( array_key_exists(self::EXEC_BEFORE, $transition) ) {
142
                $before = $transition[self::EXEC_BEFORE];
143 4
                if ($before) {
144
                    ($before)($this);
145 4
                }
146
            }
147
            if( array_key_exists(self::EXEC_ACTION, $transition) ) {
148 1
                $action = $transition[self::EXEC_ACTION];
149
                if ($action) {
150 1
                    ($action)($this);
151
                }
152
            }
153 1
            if( array_key_exists(self::EXEC_AFTER, $transition) ) {
154
                $after = $transition[self::EXEC_AFTER];
155 1
                if ($after) {
156
                    ($after)($this);
157
                }
158 26
            }
159
        }
160 26
161 25
        if( $this->cancelTransition || $wasGuarded ){
162 24
            $this->cancelTransition = false;
163
        } else {
164 26
            $this->currentState = $transition[self::NEXT_STATE];
165
        }
166 26
167 4
        $this->transitionInProgress = false;
168 25
        $event = array_shift($this->eventsQueued);
169
        if(  $event != null ){
170 25
            $this->fireEvent($event);
171
        }
172 25
173 2
        return $this;
174 24
    }
175
176 16
    public function can($event)
177
    {
178 16
        try{
179 2
            $this->eventMustExistOrFail($event);
180 15
        } catch(\InvalidArgumentException $e){
181
            return false;
182 14
        }
183
184 14
        $transition = $this->sm[$this->currentState][$event];
185 1
        if( ! array_key_exists(self::EXEC_GUARD, $transition)){
186 13
            return true;
187
        }
188 24
189
        $can = true;
190 24
        $guard = $transition[self::EXEC_GUARD];
191 24
        if( $guard ){
192
            if( ($guard)($this) === false )
193 24
                $can = false;
194
        }
195
        return $can;
196
    }
197
198
    public function cancelTransition()
199
    {
200
        $this->cancelTransition = true;
201
    }
202
203
    public function getCurrentState()
204
    {
205
        return $this->currentState;
206
    }
207
208
    public function getCurrentEvent()
209
    {
210
        return $this->currentEvent;
211
    }
212
213
    public function getNextState()
214
    {
215
        return $this->nextState;
216
    }
217
218
    public function getMachineToArray()
219
    {
220
        return $this->sm;
221
    }
222
223
    /**
224
     * All possible transitions from current state.
225
     *
226
     * @return array
227
     */
228
    /*public function getPossibleTransitions(){
229
        // TODO: pending to implementation
230
    }*/
231
232
    private function argumentIsValidOrFail($arg): void
233
    {
234
        $this->argumentIsNotNullOrFail($arg);
235
        $this->argumentIsNotBlankOrFail($arg);
236
    }
237
238
    private function argumentIsNotNullOrFail($arg): void
239
    {
240
        if( $arg === null )
241
            throw new \InvalidArgumentException("Null is not an valid argument");
242
    }
243
244
    private function argumentIsNotBlankOrFail($arg): void
245
    {
246
        if( trim($arg) === "" )
247
            throw new \InvalidArgumentException("Blank is not an valid argument");
248
    }
249
250
    private function eventMustExistOrFail($event)
251
    {
252
        if( !( isset($this->sm[$this->currentState][$event]) ) )
253
            throw new \InvalidArgumentException("Unexpected event '{$event}' on '{$this->currentState}' state");
254
    }
255
256
    private function stateMustExistOrFail($state)
257
    {
258
        if( ! isset($this->sm[$state]) )
259
            throw new \InvalidArgumentException("Event '{$this->currentEvent}' fired an unexpected '{$state}' state");
260
    }
261
262
    private function setCurrentStateIfThisIsInitialState($state): void
263
    {
264
        if( $this->currentState == null){
265
            $this->currentState = $state;
266
        }
267
    }
268
}
269