Completed
Branch master (755019)
by Oliver
04:40
created

Statemachine::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.0541

Importance

Changes 6
Bugs 0 Features 1
Metric Value
c 6
b 0
f 1
dl 0
loc 20
ccs 9
cts 11
cp 0.8182
rs 9.4286
cc 3
eloc 16
nc 4
nop 4
crap 3.0541
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\Statemachine\Exception\WrongEventForStateException;
9
use Metabor\Statemachine\Factory\TransitionSelector\OneOrNoneActiveTransition;
10
use Metabor\Statemachine\Transition\ActiveTransitionFilter;
11
use MetaborStd\Event\DispatcherInterface;
12
use MetaborStd\Event\EventInterface;
13
use MetaborStd\NamedInterface;
14
use MetaborStd\Statemachine\Factory\TransitionSelectorInterface;
15
use MetaborStd\Statemachine\ProcessInterface;
16
use MetaborStd\Statemachine\StateInterface;
17
use MetaborStd\Statemachine\StatemachineInterface;
18
use MetaborStd\Statemachine\TransitionInterface;
19
20
/**
21
 * @author Oliver Tischlinger
22
 */
23
class Statemachine extends Subject implements StatemachineInterface
24
{
25
    /**
26
     * @var object
27
     */
28
    private $subject;
29
30
    /**
31
     * @var StateInterface
32
     */
33
    private $currentState;
34
35
    /**
36
     * @var StateInterface
37
     */
38
    private $lastState;
39
40
    /**
41
     * @var DispatcherInterface
42
     */
43
    private $dispatcher;
44
45
    /**
46
     * @var EventInterface
47
     */
48
    private $currentEvent;
49
50
    /**
51
     * @var \ArrayAccess
52
     */
53
    private $currentContext;
54
55
    /**
56
     * @var TransitionSelectorInterface
57
     */
58
    private $transitonSelector;
59
60
    /**
61
     * @var TransitionInterface
62
     */
63
    private $selectedTransition;
64
65
    /**
66
     * @var ProcessInterface
67
     */
68
    private $process;
69
70
    /**
71
     * @param object           $subject
72
     * @param ProcessInterface $process
73
     * @param string           $stateName
74
     */
75 9
    public function __construct(
76
        $subject,
77
        ProcessInterface $process,
78
        $stateName = null,
79
        TransitionSelectorInterface $transitonSelector = null
80
    ) {
81 9
        parent::__construct();
82 9
        $this->subject = $subject;
83 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...
84
            $this->currentState = $process->getState($stateName);
85
        } else {
86 9
            $this->currentState = $process->getInitialState();
87
        }
88 9
        if ($transitonSelector) {
89
            $this->transitonSelector = $transitonSelector;
90
        } else {
91 9
            $this->transitonSelector = new OneOrNoneActiveTransition();
92
        }
93 9
        $this->process = $process;
94 9
    }
95
96
    /**
97
     * @return ProcessInterface
98
     */
99
    public function getProcess()
100
    {
101
        return $this->process;
102
    }
103
104
    /**
105
     * @see MetaborStd\Statemachine.StatemachineInterface::getCurrentState()
106
     */
107 6
    public function getCurrentState()
108
    {
109 6
        return $this->currentState;
110
    }
111
112
    /**
113
     * @return StateInterface
114
     */
115 1
    public function getLastState()
116
    {
117 1
        return $this->lastState;
118
    }
119
120
    /**
121
     * @param \ArrayAccess   $context
122
     * @param EventInterface $event
123
     */
124 3
    protected function doCheckTransitions(\ArrayAccess $context, EventInterface $event = null)
125
    {
126
        try {
127 3
            $transitions = $this->currentState->getTransitions();
128 3
            $activeTransitions = new ActiveTransitionFilter($transitions, $this->getSubject(), $context, $event);
129 3
            $this->selectedTransition = $this->transitonSelector->selectTransition($activeTransitions);
130 3
            if ($this->selectedTransition) {
131 3
                $targetState = $this->selectedTransition->getTargetState();
132 3
                if ($this->currentState != $targetState) {
133 3
                    $this->lastState = $this->currentState;
134 3
                    $this->currentState = $targetState;
135 3
                    $this->currentContext = $context;
136 3
                    $this->currentEvent = $event;
137 3
                    $this->notify();
138 3
                    $this->currentContext = null;
139 3
                    $this->currentEvent = null;
140 3
                    $this->selectedTransition = null;
141 3
                    $this->lastState = null;
142
                }
143 3
                $this->checkTransitions();
144
            }
145
        } catch (\Exception $exception) {
146
            $message = 'Exception was thrown when doing a transition from current state "' . $this->currentState->getName() . '"';
147
            if ($this->currentEvent instanceof NamedInterface) {
148
                $message .= ' with event "' . $this->currentEvent->getName() . '"';
149
            }
150
            throw new \RuntimeException($message, 0, $exception);
151
        }
152 3
    }
153
154
    /**
155
     * @return \MetaborStd\Statemachine\TransitionInterface
156
     */
157 1
    public function getSelectedTransition()
158
    {
159 1
        return $this->selectedTransition;
160
    }
161
162
    /**
163
     * is called after dispatcher was executed.
164
     */
165 3
    public function onDispatcherReady()
166
    {
167 3
        if ($this->dispatcher && $this->dispatcher->isReady()) {
168 3
            $context = $this->currentContext;
169 3
            $event = $this->currentEvent;
170 3
            $this->dispatcher = null;
171 3
            $this->currentContext = null;
172 3
            $this->currentEvent = null;
173 3
            $this->doCheckTransitions($context, $event);
174
        }
175 3
    }
176
177
    /**
178
     * @param DispatcherInterface $dispatcher
179
     * @param string              $name
180
     * @param \ArrayAccess        $context
181
     *
182
     * @throws \RuntimeException
183
     */
184 4
    public function dispatchEvent(DispatcherInterface $dispatcher, $name, \ArrayAccess $context = null)
185
    {
186 4
        if ($this->dispatcher) {
187
            throw new \RuntimeException('Event dispatching is still running!');
188
        } else {
189 4
            if ($this->currentState->hasEvent($name)) {
190 3
                $this->dispatcher = $dispatcher;
191
192 3
                if ($context) {
193
                    $this->currentContext = $context;
194
                } else {
195 3
                    $this->currentContext = new \ArrayIterator(array());
196
                }
197 3
                $this->currentEvent = $this->currentState->getEvent($name);
198
199 3
                $dispatcher->dispatch($this->currentEvent, array($this->subject, $this->currentContext), new Callback(array($this, 'onDispatcherReady')));
200
            } else {
201 1
                throw new WrongEventForStateException($this->currentState->getName(), $name);
202
            }
203
        }
204 3
    }
205
206
    /**
207
     * @see MetaborStd\Statemachine.StatemachineInterface::triggerEvent()
208
     */
209 4
    public function triggerEvent($name, \ArrayAccess $context = null)
210
    {
211 4
        $dispatcher = new Dispatcher();
212 4
        $this->dispatchEvent($dispatcher, $name, $context);
213 3
        $dispatcher();
214 3
    }
215
216
    /**
217
     * @see MetaborStd\Statemachine.StatemachineInterface::checkTransitions()
218
     */
219 3
    public function checkTransitions()
220
    {
221 3
        $context = new \ArrayIterator(array());
222 3
        $this->doCheckTransitions($context);
223 3
    }
224
225
    /**
226
     * @see \MetaborStd\Statemachine\StatemachineInterface::getSubject()
227
     */
228 6
    public function getSubject()
229
    {
230 6
        return $this->subject;
231
    }
232
233
    /**
234
     * @return \ArrayAccess
235
     */
236
    public function getCurrentContext()
237
    {
238
        return $this->currentContext;
239
    }
240
}
241