Statemachine::isAutoreleaseLock()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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\LockCanNotBeAcquiredException;
10
use Metabor\Statemachine\Exception\WrongEventForStateException;
11
use Metabor\Statemachine\Factory\TransitionSelector\OneOrNoneActiveTransition;
12
use Metabor\Statemachine\Transition\ActiveTransitionFilter;
13
use MetaborStd\Event\DispatcherInterface;
14
use MetaborStd\Event\EventInterface;
15
use MetaborStd\NamedInterface;
16
use MetaborStd\Semaphore\MutexInterface;
17
use MetaborStd\Statemachine\Factory\TransitionSelectorInterface;
18
use MetaborStd\Statemachine\ProcessInterface;
19
use MetaborStd\Statemachine\StateInterface;
20
use MetaborStd\Statemachine\StatemachineInterface;
21
use MetaborStd\Statemachine\TransitionInterface;
22
23
/**
24
 * @author Oliver Tischlinger
25
 */
26
class Statemachine extends Subject implements StatemachineInterface
27
{
28
    /**
29
     * @var object
30
     */
31
    private $subject;
32
33
    /**
34
     * @var StateInterface
35
     */
36
    private $currentState;
37
38
    /**
39
     * @var StateInterface
40
     */
41
    private $lastState;
42
43
    /**
44
     * @var DispatcherInterface
45
     */
46
    private $dispatcher;
47
48
    /**
49
     * @var EventInterface
50
     */
51
    private $currentEvent;
52
53
    /**
54
     * @var \ArrayAccess
55
     */
56
    private $currentContext;
57
58
    /**
59
     * @var TransitionSelectorInterface
60
     */
61
    private $transitonSelector;
62
63
    /**
64
     * @var TransitionInterface
65
     */
66
    private $selectedTransition;
67
68
    /**
69
     * @var ProcessInterface
70
     */
71
    private $process;
72
73
    /**
74
     * @var MutexInterface
75
     */
76
    private $mutex;
77
78
    /**
79
     * @var bool
80
     */
81
    private $autoreleaseLock = true;
82
83
    /**
84
     * @param object                      $subject
85
     * @param ProcessInterface            $process
86
     * @param string                      $stateName
87
     * @param TransitionSelectorInterface $transitonSelector
88
     * @param MutexInterface              $mutex
89
     */
90 10
    public function __construct(
91
        $subject,
92
        ProcessInterface $process,
93
        $stateName = null,
94
        TransitionSelectorInterface $transitonSelector = null,
95
        MutexInterface $mutex = null
96
    ) {
97 10
        parent::__construct();
98 10
        $this->subject = $subject;
99 10
        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...
100
            $this->currentState = $process->getState($stateName);
101
        } else {
102 10
            $this->currentState = $process->getInitialState();
103
        }
104 10
        if ($transitonSelector) {
105
            $this->transitonSelector = $transitonSelector;
106
        } else {
107 10
            $this->transitonSelector = new OneOrNoneActiveTransition();
108
        }
109 10
        $this->process = $process;
110 10
        if ($mutex) {
111
            $this->mutex = $mutex;
112
        } else {
113 10
            $this->mutex = new NullMutex();
114
        }
115 10
    }
116
117
    /**
118
     * @return ProcessInterface
119
     */
120
    public function getProcess()
121
    {
122
        return $this->process;
123
    }
124
125
    /**
126
     * @see MetaborStd\Statemachine.StatemachineInterface::getCurrentState()
127
     */
128 6
    public function getCurrentState()
129
    {
130 6
        return $this->currentState;
131
    }
132
133
    /**
134
     * @return StateInterface
135
     */
136 1
    public function getLastState()
137
    {
138 1
        return $this->lastState;
139
    }
140
141
    /**
142
     * @param \ArrayAccess   $context
143
     * @param EventInterface $event
144
     */
145 3
    protected function doCheckTransitions(\ArrayAccess $context, EventInterface $event = null)
146
    {
147
        try {
148 3
            $transitions = $this->currentState->getTransitions();
149 3
            $activeTransitions = new ActiveTransitionFilter($transitions, $this->getSubject(), $context, $event);
150 3
            $this->selectedTransition = $this->transitonSelector->selectTransition($activeTransitions);
151 3
            if ($this->selectedTransition) {
152 3
                $targetState = $this->selectedTransition->getTargetState();
153 3
                if ($this->currentState != $targetState) {
154 3
                    $this->lastState = $this->currentState;
155 3
                    $this->currentState = $targetState;
156 3
                    $this->currentContext = $context;
157 3
                    $this->currentEvent = $event;
158 3
                    $this->notify();
159 3
                    $this->currentContext = null;
160 3
                    $this->currentEvent = null;
161 3
                    $this->selectedTransition = null;
162 3
                    $this->lastState = null;
163 3
                }
164 3
                $this->doCheckTransitions($context);
165 3
            }
166 3
        } catch (\Exception $exception) {
167
            $message = 'Exception was thrown when doing a transition from current state "' . $this->currentState->getName() . '"';
168
            if ($this->currentEvent instanceof NamedInterface) {
169
                $message .= ' with event "' . $this->currentEvent->getName() . '"';
170
            }
171
            if ($this->subject instanceof NamedInterface) {
172
                $message .= ' for "' . $this->subject->getName() . '"';
173
            }
174
            throw new \RuntimeException($message, 0, $exception);
175
        }
176 3
    }
177
178
    /**
179
     * @return \MetaborStd\Statemachine\TransitionInterface
180
     */
181 1
    public function getSelectedTransition()
182
    {
183 1
        return $this->selectedTransition;
184
    }
185
186
    /**
187
     * is called after dispatcher was executed.
188
     */
189 3
    public function onDispatcherReady()
190
    {
191 3
        if ($this->dispatcher && $this->dispatcher->isReady()) {
192 3
            $context = $this->currentContext;
193 3
            $event = $this->currentEvent;
194 3
            $this->dispatcher = null;
195 3
            $this->currentContext = null;
196 3
            $this->currentEvent = null;
197 3
            $this->doCheckTransitions($context, $event);
198 3
            if ($this->autoreleaseLock) {
199 3
                $this->releaseLock();
200 3
            }
201 3
        }
202 3
    }
203
204
    /**
205
     * @param DispatcherInterface $dispatcher
206
     * @param string              $name
207
     * @param \ArrayAccess        $context
208
     *
209
     * @throws \RuntimeException
210
     */
211 4
    public function dispatchEvent(DispatcherInterface $dispatcher, $name, \ArrayAccess $context = null)
212
    {
213 4
        if ($this->dispatcher) {
214
            throw new \RuntimeException('Event dispatching is still running!');
215
        } else {
216 4
            if ($this->currentState->hasEvent($name)) {
217 3
                $this->acquireLockOrThrowException();
218 3
                $this->dispatcher = $dispatcher;
219
220 3
                if ($context) {
221 1
                    $this->currentContext = $context;
222 1
                } else {
223 3
                    $this->currentContext = new \ArrayIterator(array());
224
                }
225 3
                $this->currentEvent = $this->currentState->getEvent($name);
226
227 3
                $dispatcher->dispatch($this->currentEvent, array($this->subject, $this->currentContext), new Callback(array($this, 'onDispatcherReady')));
228 3
            } else {
229 1
                throw new WrongEventForStateException($this->currentState->getName(), $name);
230
            }
231
        }
232 3
    }
233
234
    /**
235
     * @param string $name
236
     * @param \ArrayAccess|null $context
237
     */
238 4
    public function triggerEvent($name, \ArrayAccess $context = null)
239
    {
240 4
        $dispatcher = new Dispatcher();
241 4
        $this->dispatchEvent($dispatcher, $name, $context);
242 3
        $dispatcher();
243 3
    }
244
245
    /**
246
     * @param \ArrayAccess|null $context
247
     */
248 1
    public function checkTransitions(\ArrayAccess $context = null)
249
    {
250 1
        $this->acquireLockOrThrowException();
251 1
        if (!$context) {
252 1
            $context = new \ArrayIterator(array());
253 1
        }
254 1
        $this->doCheckTransitions($context);
255 1
        if ($this->autoreleaseLock) {
256 1
            $this->releaseLock();
257 1
        }
258 1
    }
259
260
    /**
261
     * @see \MetaborStd\Statemachine\StatemachineInterface::getSubject()
262
     */
263 6
    public function getSubject()
264
    {
265 6
        return $this->subject;
266
    }
267
268
    /**
269
     * @return \ArrayAccess
270
     */
271 1
    public function getCurrentContext()
272
    {
273 1
        return $this->currentContext;
274
    }
275
276
    /**
277
     * @throws LockCanNotBeAcquiredException
278
     */
279 3
    protected function acquireLockOrThrowException()
280
    {
281 3
        if (!$this->acquireLock()) {
282
            throw new LockCanNotBeAcquiredException('Lock can not be acquired!');
283
        }
284 3
    }
285
286
    /**
287
     * @return bool
288
     */
289 2
    public function isAutoreleaseLock()
290
    {
291 2
        return $this->autoreleaseLock;
292
    }
293
294
    /**
295
     * @param bool $autoreleaseLock
296
     */
297 2
    public function setAutoreleaseLock($autoreleaseLock)
298
    {
299 2
        $this->autoreleaseLock = $autoreleaseLock;
300 2
    }
301
302
    /**
303
     * Use this function if you want to aquire lock before calling triggerEvent or checkTransitions.
304
     * Lock is aquired automatically when calling dispatchEvent or checkTransitions.
305
     *
306
     * @return bool
307
     */
308 4
    public function acquireLock()
309
    {
310 4
        if ($this->mutex->isAcquired()) {
311 1
            return true;
312
        } else {
313 4
            return $this->mutex->acquireLock();
314
        }
315
    }
316
317
    /**
318
     * Manuall release of the lock.
319
     * Can be used for example if you disable autorelase of locks
320
     */
321 4
    public function releaseLock()
322
    {
323 4
        $this->mutex->releaseLock();
324 4
    }
325
326
    /**
327
     * @return bool
328
     */
329 1
    public function isLockAcquired()
330
    {
331 1
        return $this->mutex->isAcquired();
332
    }
333
}
334