Completed
Push — master ( fc63d4...58d04b )
by Yohan
06:35 queued 04:20
created

StateMachine::getDispatcher()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Finite\StateMachine;
4
5
use Finite\Event\FiniteEvents;
6
use Finite\Event\StateMachineEvent;
7
use Finite\Event\TransitionEvent;
8
use Finite\Exception;
9
use Finite\State\Accessor\PropertyPathStateAccessor;
10
use Finite\State\Accessor\StateAccessorInterface;
11
use Finite\State\State;
12
use Finite\State\StateInterface;
13
use Finite\Transition\Transition;
14
use Finite\Transition\TransitionInterface;
15
use Symfony\Component\EventDispatcher\EventDispatcher;
16
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
17
18
/**
19
 * The Finite State Machine
20
 *
21
 * @author Yohan Giarelli <[email protected]>
22
 */
23
class StateMachine implements StateMachineInterface
0 ignored issues
show
Complexity introduced by
The class StateMachine has a coupling between objects value of 20. Consider to reduce the number of dependencies under 13.
Loading history...
24
{
25
    /**
26
     * The stateful object
27
     *
28
     * @var object
29
     */
30
    protected $object;
31
32
    /**
33
     * The available states
34
     *
35
     * @var array
36
     */
37
    protected $states = array();
38
39
    /**
40
     * The available transitions
41
     *
42
     * @var array
43
     */
44
    protected $transitions = array();
45
46
    /**
47
     * The current state
48
     *
49
     * @var StateInterface
50
     */
51
    protected $currentState;
52
53
    /**
54
     * @var EventDispatcherInterface
55
     */
56
    protected $dispatcher;
57
58
    /**
59
     * @var StateAccessorInterface
60
     */
61
    protected $stateAccessor;
62
63
    /**
64
     * @var string
65
     */
66
    protected $graph;
67
68
    /**
69
     * @param object                   $object
70
     * @param EventDispatcherInterface $dispatcher
71
     * @param StateAccessorInterface   $stateAccessor
72
     */
73 200
    public function __construct(
74
        $object = null,
75
        EventDispatcherInterface $dispatcher = null,
76
        StateAccessorInterface $stateAccessor = null
77
    )
78
    {
79 200
        $this->object        = $object;
80 200
        $this->dispatcher    = $dispatcher ?: new EventDispatcher;
81 200
        $this->stateAccessor = $stateAccessor ?: new PropertyPathStateAccessor;
82 200
    }
83
84
    /**
85
     * @{inheritDoc}
86
     */
87 145
    public function initialize()
88
    {
89 145
        if (null === $this->object) {
90
            throw new Exception\ObjectException('No object bound to the State Machine');
91
        }
92
93
        try {
94 145
            $initialState = $this->stateAccessor->getState($this->object);
95 116
        } catch (Exception\NoSuchPropertyException $e) {
96
            throw new Exception\ObjectException(sprintf(
97
               'StateMachine can\'t be initialized because the defined property_path of object "%s" does not exist.',
98
                get_class($this->object)
99
            ), $e->getCode(), $e);
100
        }
101
102 145
        if (null === $initialState) {
103 20
            $initialState = $this->findInitialState();
104 20
            $this->stateAccessor->setState($this->object, $initialState);
105
106 20
            $this->dispatcher->dispatch(FiniteEvents::SET_INITIAL_STATE, new StateMachineEvent($this));
107 16
        }
108
109 145
        $this->currentState = $this->getState($initialState);
110
111 145
        $this->dispatcher->dispatch(FiniteEvents::INITIALIZE, new StateMachineEvent($this));
112 145
    }
113
114
    /**
115
     * {@inheritDoc}
116
     *
117
     * @throws Exception\StateException
118
     */
119 20
    public function apply($transitionName, array $parameters = array())
120
    {
121 20
        $transition = $this->getTransition($transitionName);
122 20
        $event      = new TransitionEvent($this->getCurrentState(), $transition, $this, $parameters);
123 20
        if (!$this->can($transition, $parameters)) {
124 5
            throw new Exception\StateException(sprintf(
125 5
                'The "%s" transition can not be applied to the "%s" state of object "%s" with graph "%s".',
126 5
                $transition->getName(),
127 5
                $this->currentState->getName(),
128 5
                get_class($this->getObject()),
129 5
                $this->getGraph()
130 4
            ));
131
        }
132
133 20
        $this->dispatcher->dispatch(FiniteEvents::PRE_TRANSITION, $event);
134 20
        $this->dispatcher->dispatch(FiniteEvents::PRE_TRANSITION . '.' . $transitionName, $event);
135 20
        if (null !== $this->getGraph()) {
136 10
            $this->dispatcher->dispatch(FiniteEvents::PRE_TRANSITION . '.' . $this->getGraph() . '.' . $transition->getName(), $event);
137 8
        }
138
139 20
        $returnValue = $transition->process($this);
140 20
        $this->stateAccessor->setState($this->object, $transition->getState());
141 20
        $this->currentState = $this->getState($transition->getState());
142
143 20
        $this->dispatcher->dispatch(FiniteEvents::POST_TRANSITION, $event);
144 20
        $this->dispatcher->dispatch(FiniteEvents::POST_TRANSITION . '.' . $transitionName, $event);
145 20
        if (null !== $this->getGraph()) {
146 10
            $this->dispatcher->dispatch(FiniteEvents::POST_TRANSITION . '.' . $this->getGraph() . '.' . $transition->getName(), $event);
147 8
        }
148
149 20
        return $returnValue;
150
    }
151
152
    /**
153
     * {@inheritDoc}
154
     */
155 55
    public function can($transition, array $parameters = array())
156
    {
157 55
        $transition = $transition instanceof TransitionInterface ? $transition : $this->getTransition($transition);
158
159 55
        if (null !== $transition->getGuard() && !call_user_func($transition->getGuard(), $this)) {
160 5
            return false;
161
        }
162
163 50
        if (!in_array($transition->getName(), $this->getCurrentState()->getTransitions())) {
164 30
            return false;
165
        }
166
167 50
        $event = new TransitionEvent($this->getCurrentState(), $transition, $this, $parameters);
168 50
        $this->dispatcher->dispatch(FiniteEvents::TEST_TRANSITION, $event);
169 50
        $this->dispatcher->dispatch(FiniteEvents::TEST_TRANSITION . '.' . $transition->getName(), $event);
170 50
        if (null !== $this->getGraph()) {
171 10
            $this->dispatcher->dispatch(FiniteEvents::TEST_TRANSITION . '.' . $this->getGraph() . '.' . $transition->getName(), $event);
172 8
        }
173
174 50
        return !$event->isRejected();
175
    }
176
177
    /**
178
     * @{inheritDoc}
179
     */
180 165
    public function addState($state)
181
    {
182 165
        if (!$state instanceof StateInterface) {
183 160
            $state = new State($state);
184 128
        }
185
186 165
        $this->states[$state->getName()] = $state;
187 165
    }
188
189
    /**
190
     * @{inheritDoc}
191
     */
192 160
    public function addTransition($transition, $initialState = null, $finalState = null)
193
    {
194 160
        if ((null === $initialState || null === $finalState) && !$transition instanceof TransitionInterface) {
195
            throw new \InvalidArgumentException(
196
                'You must provide a TransitionInterface instance or the $transition, ' .
197
                '$initialState and $finalState parameters'
198
            );
199
        }
200
        // If transition isn't a TransitionInterface instance, we create one from the states date
201 160
        if (!$transition instanceof TransitionInterface) {
202
            try {
203 140
                $transition = $this->getTransition($transition);
204 140
            } catch (Exception\TransitionException $e) {
205 140
                $transition = new Transition($transition, $initialState, $finalState);
0 ignored issues
show
Documentation introduced by
$transition is of type object<Finite\Transition\TransitionInterface>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
206
            }
207 112
        }
208
209 160
        $this->transitions[$transition->getName()] = $transition;
210
211
        // We add missings states to the State Machine
212
        try {
213 160
            $this->getState($transition->getState());
214 145
        } catch (Exception\StateException $e) {
215 85
            $this->addState($transition->getState());
216
        }
217 160
        foreach ($transition->getInitialStates() as $state) {
218
            try {
219 160
                $this->getState($state);
220 135
            } catch (Exception\StateException $e) {
221 35
                $this->addState($state);
222
            }
223 160
            $state = $this->getState($state);
224 160
            if ($state instanceof State) {
225 160
                $state->addTransition($transition);
226 128
            }
227 128
        }
228 160
    }
229
230
    /**
231
     * @{inheritDoc}
232
     */
233 145
    public function getTransition($name)
234
    {
235 145
        if (!isset($this->transitions[$name])) {
236 140
            throw new Exception\TransitionException(sprintf(
237 140
                'Unable to find a transition called "%s" on object "%s" with graph "%s".',
238 112
                $name,
239 140
                get_class($this->getObject()),
240 140
                $this->getGraph()
241 112
            ));
242
        }
243
244 50
        return $this->transitions[$name];
245
    }
246
247
    /**
248
     * @{inheritDoc}
249
     */
250 165
    public function getState($name)
251
    {
252 165
        $name = (string) $name;
253
254 165
        if (!isset($this->states[$name])) {
255 90
            throw new Exception\StateException(sprintf(
256 90
                'Unable to find a state called "%s" on object "%s" with graph "%s".',
257 72
                $name,
258 90
                get_class($this->getObject()),
259 90
                $this->getGraph()
260 72
            ));
261
        }
262
263 165
        return $this->states[$name];
264
    }
265
266
    /**
267
     * @{inheritDoc}
268
     */
269 5
    public function getTransitions()
270
    {
271 5
        return array_keys($this->transitions);
272
    }
273
274
    /**
275
     * @{inheritDoc}
276
     */
277 5
    public function getStates()
278
    {
279 5
        return array_keys($this->states);
280
    }
281
282
    /**
283
     * {@inheritDoc}
284
     */
285 135
    public function setObject($object)
286
    {
287 135
        $this->object = $object;
288 135
    }
289
290
    /**
291
     * @{inheritDoc}
292
     */
293 160
    public function getObject()
294
    {
295 160
        return $this->object;
296
    }
297
298
    /**
299
     * @{inheritDoc}
300
     */
301 110
    public function getCurrentState()
302
    {
303 110
        return $this->currentState;
304
    }
305
306
    /**
307
     * Find and return the Initial state if exists
308
     *
309
     * @return string
310
     *
311
     * @throws Exception\StateException
312
     */
313 20
    protected function findInitialState()
314
    {
315 20
        foreach ($this->states as $state) {
316 20
            if (State::TYPE_INITIAL === $state->getType()) {
317 20
                return $state->getName();
318
            }
319
        }
320
321
        throw new Exception\StateException(sprintf(
322
            'No initial state found on object "%s" with graph "%s".',
323
            get_class($this->getObject()),
324
            $this->getGraph()
325
        ));
326
    }
327
328
    /**
329
     * @param EventDispatcherInterface $dispatcher
330
     */
331
    public function setDispatcher(EventDispatcherInterface $dispatcher)
332
    {
333
        $this->dispatcher = $dispatcher;
334
    }
335
336
    /**
337
     * @return EventDispatcherInterface
338
     */
339 5
    public function getDispatcher()
340
    {
341 5
        return $this->dispatcher;
342
    }
343
344
    /**
345
     * @param StateAccessorInterface $stateAccessor
346
     */
347 10
    public function setStateAccessor(StateAccessorInterface $stateAccessor)
348
    {
349 10
        $this->stateAccessor = $stateAccessor;
350 10
    }
351
352
    /**
353
     * @{inheritDoc}
354
     */
355 15
    public function setGraph($graph)
356
    {
357 15
        $this->graph = $graph;
358 15
    }
359
360
    /**
361
     * @{inheritDoc}
362
     */
363 160
    public function getGraph()
364
    {
365 160
        return $this->graph;
366
    }
367
}
368