This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace izzum\statemachine; |
||
3 | use izzum\command\NullCommand; |
||
4 | use izzum\rules\TrueRule; |
||
5 | use izzum\statemachine\Exception; |
||
6 | use izzum\statemachine\utils\Utils; |
||
7 | use izzum\statemachine\Context; |
||
8 | use izzum\rules\Rule; |
||
9 | use izzum\rules\AndRule; |
||
10 | use izzum\rules\izzum\rules; |
||
11 | use izzum\rules\IRule; |
||
12 | |||
13 | /** |
||
14 | * Transition class |
||
15 | * An abstraction for everything that is needed to make an allowed and succesful |
||
16 | * transition between states. |
||
17 | * |
||
18 | * It has functionality to accept a Rule (guard logic) and a Command (transition |
||
19 | * logic) as well as callables for the guard logic and transition logic . |
||
20 | * callables are: closures, anonymous functions, user defined functions, |
||
21 | * instance methods, static methods etc. see the php manual. |
||
22 | * |
||
23 | * The guards are used to check whether a transition can take place (Rule and callable) |
||
24 | * The logic parts are used to execute the transition logic (Command and callable) |
||
25 | * |
||
26 | * Rules and commands should be able to be found/autoloaded by the application |
||
27 | * |
||
28 | * If transitions share the same states (both to and from) then they should |
||
29 | * point to the same object reference (same states should share the exact same state |
||
30 | * configuration). |
||
31 | * |
||
32 | * @link https://php.net/manual/en/language.types.callable.php |
||
33 | * @link https://en.wikipedia.org/wiki/Command_pattern |
||
34 | * @author Rolf Vreijdenberger |
||
35 | * |
||
36 | */ |
||
37 | class Transition { |
||
38 | const RULE_TRUE = '\izzum\rules\TrueRule'; |
||
39 | const RULE_FALSE = '\izzum\rules\FalseRule'; |
||
40 | const RULE_EMPTY = ''; |
||
41 | const COMMAND_NULL = '\izzum\command\NullCommand'; |
||
42 | const COMMAND_EMPTY = ''; |
||
43 | const CALLABLE_NULL = null; |
||
44 | const CALLABLE_GUARD = 'transition guard'; |
||
45 | const CALLABLE_TRANSITION = 'transition logic'; |
||
46 | |||
47 | /** |
||
48 | * the state this transition starts from |
||
49 | * |
||
50 | * @var State |
||
51 | */ |
||
52 | protected $state_from; |
||
53 | |||
54 | /** |
||
55 | * the state this transition points to |
||
56 | * |
||
57 | * @var State |
||
58 | */ |
||
59 | protected $state_to; |
||
60 | |||
61 | /** |
||
62 | * an event code that can trigger this transitions |
||
63 | * |
||
64 | * @var string |
||
65 | */ |
||
66 | protected $event; |
||
67 | |||
68 | /** |
||
69 | * The fully qualified Rule class name of the |
||
70 | * Rule to be applied to check if we can transition. |
||
71 | * This can actually be a ',' seperated string of multiple rules. |
||
72 | * |
||
73 | * @var string |
||
74 | */ |
||
75 | protected $rule; |
||
76 | |||
77 | /** |
||
78 | * the fully qualified Command class name of the Command to be |
||
79 | * executed as part of the transition logic. |
||
80 | * This can actually be a ',' seperated string of multiple commands. |
||
81 | * |
||
82 | * @var string |
||
83 | */ |
||
84 | protected $command; |
||
85 | |||
86 | /** |
||
87 | * the callable to call as part of the transition logic |
||
88 | * @var callable |
||
89 | */ |
||
90 | protected $callable_transition; |
||
91 | |||
92 | /** |
||
93 | * the callable to call as part of the transition guard (should return a boolean) |
||
94 | * @var callable |
||
95 | */ |
||
96 | protected $callable_guard; |
||
97 | |||
98 | /** |
||
99 | * a description for the state |
||
100 | * |
||
101 | * @var string |
||
102 | */ |
||
103 | protected $description; |
||
104 | |||
105 | /** |
||
106 | * |
||
107 | * @param State $state_from |
||
108 | * @param State $state_to |
||
109 | * @param string $event |
||
110 | * optional: an event name by which this transition can be |
||
111 | * triggered |
||
112 | * @param string $rule |
||
113 | * optional: one or more fully qualified Rule (sub)class name(s) |
||
114 | * to check to see if we are allowed to transition. |
||
115 | * This can actually be a ',' seperated string of multiple rules |
||
116 | * that will be applied as a chained 'and' rule. |
||
117 | * @param string $command |
||
118 | * optional: one or more fully qualified Command (sub)class |
||
119 | * name(s) to execute for a transition. |
||
120 | * This can actually be a ',' seperated string of multiple |
||
121 | * commands that will be executed as a composite. |
||
122 | * @param callable $callable_guard |
||
123 | * optional: a php callable to call. eg: "function(){echo 'closure called';};" |
||
124 | * @param callable $callable_transition |
||
125 | * optional: a php callable to call. eg: "izzum\MyClass::myStaticMethod" |
||
126 | */ |
||
127 | 67 | public function __construct(State $state_from, State $state_to, $event = null, $rule = self::RULE_EMPTY, $command = self::COMMAND_EMPTY, $callable_guard = self::CALLABLE_NULL, $callable_transition = self::CALLABLE_NULL) |
|
128 | { |
||
129 | 67 | $this->state_from = $state_from; |
|
130 | 67 | $this->state_to = $state_to; |
|
131 | 67 | $this->setRuleName($rule); |
|
132 | 67 | $this->setCommandName($command); |
|
133 | 67 | $this->setGuardCallable($callable_guard); |
|
0 ignored issues
–
show
|
|||
134 | 67 | $this->setTransitionCallable($callable_transition); |
|
0 ignored issues
–
show
It seems like
$callable_transition defined by parameter $callable_transition on line 127 can also be of type null ; however, izzum\statemachine\Trans...setTransitionCallable() does only seem to accept callable , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
135 | // setup bidirectional relationship with state this transition |
||
136 | // originates from. only if it's not a regex or final state type |
||
137 | 67 | if (!$state_from->isRegex() && !$state_from->isFinal()) { |
|
138 | 67 | $state_from->addTransition($this); |
|
139 | 67 | } |
|
140 | // set and sanitize event name |
||
141 | 67 | $this->setEvent($event); |
|
142 | 67 | } |
|
143 | |||
144 | /** |
||
145 | * the callable to call as part of the transition logic |
||
146 | * @param callable $callable |
||
147 | */ |
||
148 | 67 | public function setTransitionCallable($callable) { |
|
149 | 67 | $this->callable_transition = $callable; |
|
150 | 67 | return $this; |
|
151 | } |
||
152 | |||
153 | /** |
||
154 | * returns the callable for the transition logic. |
||
155 | * @return callable or null |
||
156 | */ |
||
157 | 38 | public function getTransitionCallable() |
|
158 | { |
||
159 | 38 | return $this->callable_transition; |
|
160 | } |
||
161 | |||
162 | /** |
||
163 | * the callable to call as part of the transition guard |
||
164 | * @param callable $callable |
||
165 | */ |
||
166 | 67 | public function setGuardCallable($callable) { |
|
167 | 67 | $this->callable_guard = $callable; |
|
168 | 67 | return $this; |
|
169 | } |
||
170 | |||
171 | /** |
||
172 | * returns the callable for the guard logic. |
||
173 | * @return callable or null |
||
174 | */ |
||
175 | 36 | public function getGuardCallable() |
|
176 | { |
||
177 | 36 | return $this->callable_guard; |
|
178 | } |
||
179 | |||
180 | /** |
||
181 | * Can this transition be triggered by a certain event? |
||
182 | * This also matches on the transition name. |
||
183 | * |
||
184 | * @param string $event |
||
185 | * @return boolean |
||
186 | */ |
||
187 | 12 | public function isTriggeredBy($event) |
|
188 | { |
||
189 | 12 | return ($this->event === $event || $this->getName() === $event) && $event !== null && $event !== ''; |
|
190 | } |
||
191 | |||
192 | /** |
||
193 | * is a transition possible? Check the guard Rule with the domain object |
||
194 | * injected. |
||
195 | * |
||
196 | * @param Context $context |
||
197 | * @return boolean |
||
198 | */ |
||
199 | 23 | public function can(Context $context) |
|
200 | { |
||
201 | try { |
||
202 | 23 | if(!$this->getRule($context)->applies()) { |
|
203 | 5 | return false; |
|
204 | } |
||
205 | 20 | return $this->callCallable($this->getGuardCallable(), $context, self::CALLABLE_GUARD); |
|
206 | 4 | } catch(\Exception $e) { |
|
207 | //rule or callable failure |
||
208 | 4 | $e = new Exception($this->toString() . ' '. $e->getMessage(), Exception::RULE_APPLY_FAILURE, $e); |
|
209 | 4 | throw $e; |
|
210 | } |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Process the transition for the statemachine and execute the associated |
||
215 | * Command with the domain object injected. |
||
216 | * |
||
217 | * @param Context $context |
||
218 | * @return void |
||
219 | */ |
||
220 | 23 | public function process(Context $context) |
|
221 | { |
||
222 | // execute, we do not need to check if we 'can' since this is done |
||
223 | // by the statemachine itself |
||
224 | try { |
||
225 | 23 | $this->getCommand($context)->execute(); |
|
226 | 22 | $this->callCallable($this->getTransitionCallable(), $context, self::CALLABLE_TRANSITION); |
|
227 | 23 | } catch(\Exception $e) { |
|
228 | // command or callable failure |
||
229 | 2 | $e = new Exception($e->getMessage(), Exception::COMMAND_EXECUTION_FAILURE, $e); |
|
230 | 2 | throw $e; |
|
231 | } |
||
232 | 21 | } |
|
233 | |||
234 | /** |
||
235 | * calls the $callable as part of the transition |
||
236 | * @param callable $callable |
||
237 | * @param Context $context |
||
238 | * @throws Exception in case of an invalid callable |
||
239 | */ |
||
240 | 24 | protected function callCallable($callable, Context $context, $type = 'n/a') { |
|
241 | //in case it is a guard callable we need to return true/false |
||
242 | 24 | if($callable != self::CALLABLE_NULL){ |
|
243 | 9 | Utils::checkCallable($callable, $type, "transition: " . $this, $context); |
|
244 | 8 | return (boolean) call_user_func($callable, $context->getEntity()); |
|
245 | } |
||
246 | 21 | return true; |
|
247 | } |
||
248 | |||
249 | /** |
||
250 | * returns the associated Rule for this Transition, |
||
251 | * configured with a 'reference' (stateful) object |
||
252 | * |
||
253 | * @param Context $context |
||
254 | * the associated Context for a our statemachine |
||
255 | * @return IRule a Rule or chained AndRule if the rule input was a ',' |
||
256 | * seperated string of rules. |
||
257 | * @throws Exception |
||
258 | */ |
||
259 | 30 | public function getRule(Context $context) |
|
260 | { |
||
261 | // if no rule is defined, just allow the transition by default |
||
262 | 30 | if ($this->rule === '' || $this->rule === null) { |
|
263 | 13 | return new TrueRule(); |
|
264 | } |
||
265 | |||
266 | 22 | $entity = $context->getEntity(); |
|
267 | |||
268 | // a rule string can be made up of multiple rules seperated by a comma |
||
269 | 22 | $all_rules = explode(',', $this->rule); |
|
270 | 22 | $rule = new TrueRule(); |
|
271 | 22 | foreach ($all_rules as $single_rule) { |
|
272 | |||
273 | // guard clause to check if rule exists |
||
274 | 22 | if (!class_exists($single_rule)) { |
|
275 | 1 | $e = new Exception(sprintf("failed rule creation, class does not exist: (%s) for Context (%s).", $this->rule, $context->toString()), Exception::RULE_CREATION_FAILURE); |
|
276 | 1 | throw $e; |
|
277 | } |
||
278 | |||
279 | try { |
||
280 | 21 | $and_rule = new $single_rule($entity); |
|
281 | // create a chain of rules that need to be true |
||
282 | 20 | $rule = new AndRule($rule, $and_rule); |
|
283 | 21 | } catch(\Exception $e) { |
|
284 | 1 | $e = new Exception(sprintf("failed rule creation, class objects to construction with entity: (%s) for Context (%s). message: %s", $this->rule, $context->toString(), $e->getMessage()), Exception::RULE_CREATION_FAILURE); |
|
285 | 1 | throw $e; |
|
286 | } |
||
287 | 20 | } |
|
288 | 20 | return $rule; |
|
289 | } |
||
290 | |||
291 | |||
292 | /** |
||
293 | * returns the associated Command for this Transition. |
||
294 | * the Command will be configured with the 'reference' of the stateful |
||
295 | * object. |
||
296 | * In case there have been multiple commands as input (',' seperated), this |
||
297 | * method will return a Composite command. |
||
298 | * |
||
299 | * @param Context $context |
||
300 | * @return izzum\command\ICommand |
||
301 | * @throws Exception |
||
302 | */ |
||
303 | 30 | public function getCommand(Context $context) |
|
304 | { |
||
305 | 30 | return Utils::getCommand($this->command, $context); |
|
306 | } |
||
307 | |||
308 | /** |
||
309 | * |
||
310 | * @return string |
||
311 | */ |
||
312 | 8 | public function toString() |
|
313 | { |
||
314 | 8 | return get_class($this) . " '" . $this->getName() . "' [event]: '" . $this->event . "'" . " [rule]: '" . $this->rule . "' [command]: '" . $this->command . "'"; |
|
315 | } |
||
316 | |||
317 | /** |
||
318 | * get the state this transition points from |
||
319 | * |
||
320 | * @return State |
||
321 | */ |
||
322 | 67 | public function getStateFrom() |
|
323 | { |
||
324 | 67 | return $this->state_from; |
|
325 | } |
||
326 | |||
327 | /** |
||
328 | * get the state this transition points to |
||
329 | * |
||
330 | * @return State |
||
331 | */ |
||
332 | 67 | public function getStateTo() |
|
333 | { |
||
334 | 67 | return $this->state_to; |
|
335 | } |
||
336 | |||
337 | /** |
||
338 | * get the transition name. |
||
339 | * the transition name is always unique for a statemachine |
||
340 | * since it constists of <state_from>_to_<state_to> |
||
341 | * |
||
342 | * @return string |
||
343 | */ |
||
344 | 67 | public function getName() |
|
345 | { |
||
346 | 67 | $name = Utils::getTransitionName($this->getStateFrom()->getName(), $this->getStateTo()->getName()); |
|
347 | 67 | return $name; |
|
348 | } |
||
349 | |||
350 | /** |
||
351 | * return the command name(s). |
||
352 | * one or more fully qualified command (sub)class name(s) to execute for a |
||
353 | * transition. |
||
354 | * This can actually be a ',' seperated string of multiple commands that |
||
355 | * will be executed as a composite. |
||
356 | * |
||
357 | * @return string |
||
358 | */ |
||
359 | 21 | public function getCommandName() |
|
360 | { |
||
361 | 21 | return $this->command; |
|
362 | } |
||
363 | |||
364 | 67 | public function setCommandName($command) |
|
365 | { |
||
366 | 67 | $this->command = trim($command); |
|
367 | 67 | return $this; |
|
368 | } |
||
369 | |||
370 | 21 | public function getRuleName() |
|
371 | { |
||
372 | 21 | return $this->rule; |
|
373 | } |
||
374 | |||
375 | 67 | public function setRuleName($rule) |
|
376 | { |
||
377 | 67 | $this->rule = trim($rule); |
|
378 | 67 | return $this; |
|
379 | } |
||
380 | |||
381 | /** |
||
382 | * set the description of the transition (for uml generation for example) |
||
383 | * |
||
384 | * @param string $description |
||
385 | */ |
||
386 | 23 | public function setDescription($description) |
|
387 | { |
||
388 | 23 | $this->description = $description; |
|
389 | 23 | return $this; |
|
390 | } |
||
391 | |||
392 | /** |
||
393 | * get the description for this transition (if any) |
||
394 | * |
||
395 | * @return string |
||
396 | */ |
||
397 | 22 | public function getDescription() |
|
398 | { |
||
399 | 22 | return $this->description; |
|
400 | } |
||
401 | |||
402 | /** |
||
403 | * set the event name by which this transition can be triggered. |
||
404 | * In case the event name is null or an empty string, it defaults to the |
||
405 | * transition name. |
||
406 | * |
||
407 | * @param string $event |
||
408 | */ |
||
409 | 67 | public function setEvent($event) |
|
410 | { |
||
411 | 67 | if ($event === null || $event === '') { |
|
412 | 46 | $event = $this->getName(); |
|
413 | 46 | } |
|
414 | 67 | $this->event = $event; |
|
415 | 67 | return $this; |
|
416 | } |
||
417 | |||
418 | /** |
||
419 | * get the event name by which this transition can be triggered |
||
420 | * |
||
421 | * @return string |
||
422 | */ |
||
423 | 22 | public function getEvent() |
|
424 | { |
||
425 | 22 | return $this->event; |
|
426 | } |
||
427 | |||
428 | /** |
||
429 | * for transitions that contain regex states, we need to be able to copy an |
||
430 | * existing (subclass of this) transition with all it's fields. |
||
431 | * We need to instantiate it with a different from and to state since either |
||
432 | * one of those states can be the regex states. All other fields need to be |
||
433 | * copied. |
||
434 | * |
||
435 | * Override this method in a subclass to add other fields. By using 'new |
||
436 | * static' we are already instantiating a possible subclass. |
||
437 | * |
||
438 | * |
||
439 | * @param State $from |
||
440 | * @param State $to |
||
441 | * @return Transition |
||
442 | */ |
||
443 | 19 | public function getCopy(State $from, State $to) |
|
444 | { |
||
445 | 19 | $copy = new static($from, $to, $this->getEvent(), $this->getRuleName(), $this->getCommandName(), $this->getGuardCallable(), $this->getTransitionCallable()); |
|
446 | 19 | $copy->setDescription($this->getDescription()); |
|
447 | 19 | return $copy; |
|
448 | } |
||
449 | |||
450 | /** |
||
451 | * |
||
452 | * @return string |
||
453 | */ |
||
454 | 11 | public function __toString() |
|
455 | { |
||
456 | 11 | return $this->getName(); |
|
457 | } |
||
458 | } |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.