Completed
Push — add_lock_to_statemachine ( 571155 )
by Oliver
02:16
created

Statemachine::__construct()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 26
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.7032

Importance

Changes 7
Bugs 0 Features 2
Metric Value
c 7
b 0
f 2
dl 0
loc 26
ccs 11
cts 17
cp 0.6471
rs 8.5806
cc 4
eloc 21
nc 8
nop 5
crap 4.7032
1
<?php
2
3
namespace Metabor\Statemachine;
4
5
use Metabor\Callback\Callback;
6
use Metabor\Event\Dispatcher;
7
use Metabor\Observer\Subject;
8
use Metabor\Semaphore\NullMutex;
9
use Metabor\Statemachine\Exception\WrongEventForStateException;
10
use Metabor\Statemachine\Factory\TransitionSelector\OneOrNoneActiveTransition;
11
use Metabor\Statemachine\Transition\ActiveTransitionFilter;
12
use MetaborStd\Event\DispatcherInterface;
13
use MetaborStd\Event\EventInterface;
14
use MetaborStd\NamedInterface;
15
use MetaborStd\Semaphore\MutexInterface;
16
use MetaborStd\Statemachine\Factory\TransitionSelectorInterface;
17
use MetaborStd\Statemachine\ProcessInterface;
18
use MetaborStd\Statemachine\StateInterface;
19
use MetaborStd\Statemachine\StatemachineInterface;
20
use MetaborStd\Statemachine\TransitionInterface;
21
22
/**
23
 * @author Oliver Tischlinger
24
 */
25
class Statemachine extends Subject implements StatemachineInterface
26
{
27
    /**
28
     * @var object
29
     */
30
    private $subject;
31
32
    /**
33
     * @var StateInterface
34
     */
35
    private $currentState;
36
37
    /**
38
     * @var StateInterface
39
     */
40
    private $lastState;
41
42
    /**
43
     * @var DispatcherInterface
44
     */
45
    private $dispatcher;
46
47
    /**
48
     * @var EventInterface
49
     */
50
    private $currentEvent;
51
52
    /**
53
     * @var \ArrayAccess
54
     */
55
    private $currentContext;
56
57
    /**
58
     * @var TransitionSelectorInterface
59
     */
60
    private $transitonSelector;
61
62
    /**
63
     * @var TransitionInterface
64
     */
65
    private $selectedTransition;
66
67
    /**
68
     * @var ProcessInterface
69
     */
70
    private $process;
71
72
    /**
73
     * @var MutexInterface
74
     */
75
    private $mutex;
76
77
    /**
78
     * @param object                      $subject
79
     * @param ProcessInterface            $process
80
     * @param string                      $stateName
81
     * @param TransitionSelectorInterface $transitonSelector
82
     * @param MutexInterface              $mutex
83
     */
84 9
    public function __construct(
85
        $subject,
86
        ProcessInterface $process,
87
        $stateName = null,
88
        TransitionSelectorInterface $transitonSelector = null,
89
        MutexInterface $mutex = null
90
    ) {
91 9
        parent::__construct();
92 9
        $this->subject = $subject;
93 9
        if ($stateName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stateName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
94
            $this->currentState = $process->getState($stateName);
95
        } else {
96 9
            $this->currentState = $process->getInitialState();
97
        }
98 9
        if ($transitonSelector) {
99
            $this->transitonSelector = $transitonSelector;
100
        } else {
101 9
            $this->transitonSelector = new OneOrNoneActiveTransition();
102
        }
103 9
        $this->process = $process;
104 9
        if ($mutex) {
105
            $this->mutex = $mutex;
106
        } else {
107 9
            $this->mutex = new NullMutex();
108
        }
109 9
    }
110
111
    /**
112
     * @return ProcessInterface
113
     */
114
    public function getProcess()
115
    {
116
        return $this->process;
117
    }
118
119
    /**
120
     * @see MetaborStd\Statemachine.StatemachineInterface::getCurrentState()
121
     */
122 6
    public function getCurrentState()
123
    {
124 6
        return $this->currentState;
125
    }
126
127
    /**
128
     * @return StateInterface
129
     */
130 1
    public function getLastState()
131
    {
132 1
        return $this->lastState;
133
    }
134
135
    /**
136
     * @param \ArrayAccess   $context
137
     * @param EventInterface $event
138
     */
139 3
    protected function doCheckTransitions(\ArrayAccess $context, EventInterface $event = null)
140
    {
141
        try {
142 3
            $transitions = $this->currentState->getTransitions();
143 3
            $activeTransitions = new ActiveTransitionFilter($transitions, $this->getSubject(), $context, $event);
144 3
            $this->selectedTransition = $this->transitonSelector->selectTransition($activeTransitions);
145 3
            if ($this->selectedTransition) {
146 3
                $targetState = $this->selectedTransition->getTargetState();
147 3
                if ($this->currentState != $targetState) {
148 3
                    $this->lastState = $this->currentState;
149 3
                    $this->currentState = $targetState;
150 3
                    $this->currentContext = $context;
151 3
                    $this->currentEvent = $event;
152 3
                    $this->notify();
153 3
                    $this->currentContext = null;
154 3
                    $this->currentEvent = null;
155 3
                    $this->selectedTransition = null;
156 3
                    $this->lastState = null;
157 3
                }
158 3
                $this->checkTransitions();
159 3
            }
160 3
        } catch (\Exception $exception) {
161
            $message = 'Exception was thrown when doing a transition from current state "' . $this->currentState->getName() . '"';
0 ignored issues
show
Coding Style introduced by
This line has 130 characters which exceeds the configured maximum of 120.
Loading history...
162
            if ($this->currentEvent instanceof NamedInterface) {
163
                $message .= ' with event "' . $this->currentEvent->getName() . '"';
164
            }
165
            throw new \RuntimeException($message, 0, $exception);
166
        }
167 3
    }
168
169
    /**
170
     * @return \MetaborStd\Statemachine\TransitionInterface
171
     */
172 1
    public function getSelectedTransition()
173
    {
174 1
        return $this->selectedTransition;
175
    }
176
177
    /**
178
     * is called after dispatcher was executed.
179
     */
180 3
    public function onDispatcherReady()
181
    {
182 3
        if ($this->dispatcher && $this->dispatcher->isReady()) {
183 3
            $context = $this->currentContext;
184 3
            $event = $this->currentEvent;
185 3
            $this->dispatcher = null;
186 3
            $this->currentContext = null;
187 3
            $this->currentEvent = null;
188 3
            $this->doCheckTransitions($context, $event);
189 3
        }
190 3
    }
191
192
    /**
193
     * @param DispatcherInterface $dispatcher
194
     * @param string              $name
195
     * @param \ArrayAccess        $context
196
     *
197
     * @throws \RuntimeException
198
     */
199 4
    public function dispatchEvent(DispatcherInterface $dispatcher, $name, \ArrayAccess $context = null)
200
    {
201 4
        if ($this->dispatcher) {
202
            throw new \RuntimeException('Event dispatching is still running!');
203
        } else {
204 4
            if ($this->currentState->hasEvent($name)) {
205 3
                $this->dispatcher = $dispatcher;
206
207 3
                if ($context) {
208
                    $this->currentContext = $context;
209
                } else {
210 3
                    $this->currentContext = new \ArrayIterator(array());
211
                }
212 3
                $this->currentEvent = $this->currentState->getEvent($name);
213
214 3
                $dispatcher->dispatch($this->currentEvent, array($this->subject, $this->currentContext), new Callback(array($this, 'onDispatcherReady')));
0 ignored issues
show
Coding Style introduced by
This line has 154 characters which exceeds the configured maximum of 120.
Loading history...
215 3
            } else {
216 1
                throw new WrongEventForStateException($this->currentState->getName(), $name);
217
            }
218
        }
219 3
    }
220
221
    /**
222
     * @see MetaborStd\Statemachine.StatemachineInterface::triggerEvent()
223
     */
224 4
    public function triggerEvent($name, \ArrayAccess $context = null)
225
    {
226 4
        $dispatcher = new Dispatcher();
227 4
        $this->dispatchEvent($dispatcher, $name, $context);
228 3
        $dispatcher();
229 3
    }
230
231
    /**
232
     * @see MetaborStd\Statemachine.StatemachineInterface::checkTransitions()
233
     */
234 3
    public function checkTransitions()
235
    {
236 3
        $context = new \ArrayIterator(array());
237 3
        $this->doCheckTransitions($context);
238 3
    }
239
240
    /**
241
     * @see \MetaborStd\Statemachine\StatemachineInterface::getSubject()
242
     */
243 6
    public function getSubject()
244
    {
245 6
        return $this->subject;
246
    }
247
248
    /**
249
     * @return \ArrayAccess
250
     */
251
    public function getCurrentContext()
252
    {
253
        return $this->currentContext;
254
    }
255
}
256