1 | <?php |
||
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 | * @param object $subject |
||
80 | * @param ProcessInterface $process |
||
81 | * @param string $stateName |
||
82 | * @param TransitionSelectorInterface $transitonSelector |
||
83 | * @param MutexInterface $mutex |
||
84 | */ |
||
85 | 9 | public function __construct( |
|
86 | $subject, |
||
87 | ProcessInterface $process, |
||
88 | $stateName = null, |
||
89 | TransitionSelectorInterface $transitonSelector = null, |
||
90 | MutexInterface $mutex = null |
||
91 | ) { |
||
92 | 9 | parent::__construct(); |
|
93 | 9 | $this->subject = $subject; |
|
94 | 9 | if ($stateName) { |
|
|
|||
95 | $this->currentState = $process->getState($stateName); |
||
96 | } else { |
||
97 | 9 | $this->currentState = $process->getInitialState(); |
|
98 | } |
||
99 | 9 | if ($transitonSelector) { |
|
100 | $this->transitonSelector = $transitonSelector; |
||
101 | } else { |
||
102 | 9 | $this->transitonSelector = new OneOrNoneActiveTransition(); |
|
103 | } |
||
104 | 9 | $this->process = $process; |
|
105 | 9 | if ($mutex) { |
|
106 | $this->mutex = $mutex; |
||
107 | } else { |
||
108 | 9 | $this->mutex = new NullMutex(); |
|
109 | } |
||
110 | 9 | } |
|
111 | |||
112 | /** |
||
113 | * @return ProcessInterface |
||
114 | */ |
||
115 | public function getProcess() |
||
119 | |||
120 | /** |
||
121 | * @see MetaborStd\Statemachine.StatemachineInterface::getCurrentState() |
||
122 | */ |
||
123 | 6 | public function getCurrentState() |
|
127 | |||
128 | /** |
||
129 | * @return StateInterface |
||
130 | */ |
||
131 | 1 | public function getLastState() |
|
135 | |||
136 | /** |
||
137 | * @param \ArrayAccess $context |
||
138 | * @param EventInterface $event |
||
139 | */ |
||
140 | 3 | protected function doCheckTransitions(\ArrayAccess $context, EventInterface $event = null) |
|
141 | { |
||
142 | try { |
||
143 | 3 | $transitions = $this->currentState->getTransitions(); |
|
144 | 3 | $activeTransitions = new ActiveTransitionFilter($transitions, $this->getSubject(), $context, $event); |
|
145 | 3 | $this->selectedTransition = $this->transitonSelector->selectTransition($activeTransitions); |
|
146 | 3 | if ($this->selectedTransition) { |
|
147 | 3 | $targetState = $this->selectedTransition->getTargetState(); |
|
148 | 3 | if ($this->currentState != $targetState) { |
|
149 | 3 | $this->lastState = $this->currentState; |
|
150 | 3 | $this->currentState = $targetState; |
|
151 | 3 | $this->currentContext = $context; |
|
152 | 3 | $this->currentEvent = $event; |
|
153 | 3 | $this->notify(); |
|
154 | 3 | $this->currentContext = null; |
|
155 | 3 | $this->currentEvent = null; |
|
156 | 3 | $this->selectedTransition = null; |
|
157 | 3 | $this->lastState = null; |
|
158 | 3 | } |
|
159 | 3 | $this->checkTransitions($context); |
|
160 | 3 | } |
|
161 | 3 | } catch (\Exception $exception) { |
|
162 | $message = 'Exception was thrown when doing a transition from current state "' . $this->currentState->getName() . '"'; |
||
163 | if ($this->currentEvent instanceof NamedInterface) { |
||
164 | $message .= ' with event "' . $this->currentEvent->getName() . '"'; |
||
165 | } |
||
166 | throw new \RuntimeException($message, 0, $exception); |
||
167 | } |
||
168 | 3 | } |
|
169 | |||
170 | /** |
||
171 | * @return \MetaborStd\Statemachine\TransitionInterface |
||
172 | */ |
||
173 | 1 | public function getSelectedTransition() |
|
177 | |||
178 | /** |
||
179 | * is called after dispatcher was executed. |
||
180 | */ |
||
181 | 3 | public function onDispatcherReady() |
|
193 | |||
194 | /** |
||
195 | * @param DispatcherInterface $dispatcher |
||
196 | * @param string $name |
||
197 | * @param \ArrayAccess $context |
||
198 | * |
||
199 | * @throws \RuntimeException |
||
200 | */ |
||
201 | 4 | public function dispatchEvent(DispatcherInterface $dispatcher, $name, \ArrayAccess $context = null) |
|
202 | { |
||
203 | 4 | if ($this->dispatcher) { |
|
204 | throw new \RuntimeException('Event dispatching is still running!'); |
||
205 | } else { |
||
206 | 4 | if ($this->currentState->hasEvent($name)) { |
|
207 | 3 | $this->acquireLockOrThrowException(); |
|
208 | 3 | $this->dispatcher = $dispatcher; |
|
209 | |||
210 | 3 | if ($context) { |
|
211 | 1 | $this->currentContext = $context; |
|
212 | 1 | } else { |
|
213 | 3 | $this->currentContext = new \ArrayIterator(array()); |
|
214 | } |
||
215 | 3 | $this->currentEvent = $this->currentState->getEvent($name); |
|
216 | |||
217 | 3 | $dispatcher->dispatch($this->currentEvent, array($this->subject, $this->currentContext), new Callback(array($this, 'onDispatcherReady'))); |
|
218 | 3 | } else { |
|
219 | 1 | throw new WrongEventForStateException($this->currentState->getName(), $name); |
|
220 | } |
||
221 | } |
||
222 | 3 | } |
|
223 | |||
224 | /** |
||
225 | * @param string $name |
||
226 | * @param \ArrayAccess|null $context |
||
227 | */ |
||
228 | 4 | public function triggerEvent($name, \ArrayAccess $context = null) |
|
234 | |||
235 | /** |
||
236 | * @param \ArrayAccess|null $context |
||
237 | */ |
||
238 | 3 | public function checkTransitions(\ArrayAccess $context = null) |
|
247 | |||
248 | /** |
||
249 | * @see \MetaborStd\Statemachine\StatemachineInterface::getSubject() |
||
250 | */ |
||
251 | 6 | public function getSubject() |
|
255 | |||
256 | /** |
||
257 | * @return \ArrayAccess |
||
258 | */ |
||
259 | 1 | public function getCurrentContext() |
|
260 | { |
||
261 | 1 | return $this->currentContext; |
|
262 | } |
||
263 | |||
264 | /** |
||
265 | * @throws LockCanNotBeAcquiredException |
||
266 | */ |
||
267 | 3 | protected function acquireLockOrThrowException() |
|
273 | |||
274 | /** |
||
275 | * Use this function if you want to aquire lock before calling triggerEvent or checkTransitions. |
||
276 | * Lock is aquired automatically when calling dispatchEvent or checkTransitions. |
||
277 | * |
||
278 | * @return bool |
||
279 | */ |
||
280 | 3 | public function acquireLock() |
|
288 | } |
||
289 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
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: