1
|
|
|
<?php |
2
|
|
|
namespace izzum\statemachine; |
3
|
|
|
use izzum\command\ICommand; |
4
|
|
|
use izzum\command\NullCommand; |
5
|
|
|
use izzum\statemachine\Exception; |
6
|
|
|
use izzum\command\Composite; |
7
|
|
|
use izzum\statemachine\utils\Utils; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* This class holds the finite state data: |
11
|
|
|
* - the name of the state |
12
|
|
|
* - the type of the state (initial/normal/final) |
13
|
|
|
* - what outgoing transitions this state has (bidirectional association |
14
|
|
|
* initiated by a Transition) |
15
|
|
|
* - class names for the entry and exit commands (if any) |
16
|
|
|
* - callables for entry and exit logic (if any) |
17
|
|
|
* |
18
|
|
|
* A State instance can (and should) be shared by multiple Transition |
19
|
|
|
* objects when it is the same State for their origin/from State. |
20
|
|
|
* |
21
|
|
|
* A State can be a regex state (or negated regex). |
22
|
|
|
* A regex state can be used in a transition and when added to a |
23
|
|
|
* statemachine the regular expression will be matched on all currently |
24
|
|
|
* known states on that statemachine and new Transitions will be added |
25
|
|
|
* to the statemachine that match the from/to state regexes. This is very |
26
|
|
|
* useful to build a lot of transitions very quickly. |
27
|
|
|
* |
28
|
|
|
* to build a full mesh of transitions (all states to all states): |
29
|
|
|
* $a = new State('a'); |
30
|
|
|
* $b = new State('b'); |
31
|
|
|
* $c = new State('c'); |
32
|
|
|
* $machine->addState($a); |
33
|
|
|
* $machine->addState($b); |
34
|
|
|
* $machine->addState($c); |
35
|
|
|
* $state_regex_all = new State('regex:|.*|'); |
36
|
|
|
* $machine->addTransition(new Transition($state_regex_all, $state_regex_all)); |
37
|
|
|
* |
38
|
|
|
* @author Rolf Vreijdenberger |
39
|
|
|
* @link https://php.net/manual/en/language.types.callable.php |
40
|
|
|
* @link https://en.wikipedia.org/wiki/Command_pattern |
41
|
|
|
* @link https://php.net/manual/en/function.preg-match.php |
42
|
|
|
* @link http://regexr.com/ for trying out regular expressions |
43
|
|
|
*/ |
44
|
|
|
class State { |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* state name if it is unknown (not configured) |
48
|
|
|
* @var string |
49
|
|
|
*/ |
50
|
|
|
const STATE_UNKNOWN = 'unknown'; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* default name for the first/only initial state (but you can specify whatever you want for your initial state) |
54
|
|
|
* @var string |
55
|
|
|
*/ |
56
|
|
|
const STATE_NEW = 'new'; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* default name for a normal final state |
60
|
|
|
* @var string |
61
|
|
|
*/ |
62
|
|
|
const STATE_DONE = 'done'; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* default exit/entry command |
66
|
|
|
* @var string |
67
|
|
|
*/ |
68
|
|
|
const COMMAND_NULL = '\izzum\command\NullCommand'; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* default exit/entry command for constructor |
72
|
|
|
* @var string |
73
|
|
|
*/ |
74
|
|
|
const COMMAND_EMPTY = ''; |
75
|
|
|
const CALLABLE_NULL = null; |
76
|
|
|
const REGEX_PREFIX = 'regex:'; |
77
|
|
|
const REGEX_PREFIX_NEGATED = 'not-regex:'; |
78
|
|
|
|
79
|
|
|
const CALLABLE_ENTRY = 'state entry'; |
80
|
|
|
const CALLABLE_EXIT = 'state exit'; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* the state types: |
84
|
|
|
* - 'initial': a statemachine has exactly 1 initial type, this is always the only |
85
|
|
|
* entrance into the statemachine. |
86
|
|
|
* - 'normal': a statemachine can have 0-n normal types. |
87
|
|
|
* - 'done': a statemachine should have at least 1 final type where it has no |
88
|
|
|
* further transitions. |
89
|
|
|
* - 'regex': a statemachine configuration could have regex states, which serve a purpose to create transitions |
90
|
|
|
* from or to multiple other states |
91
|
|
|
* |
92
|
|
|
* @var string |
93
|
|
|
*/ |
94
|
|
|
const TYPE_INITIAL = 'initial', TYPE_NORMAL = 'normal', TYPE_FINAL = 'final', TYPE_REGEX = 'regex'; |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* The state type: |
98
|
|
|
* - State::TYPE_INITIAL |
99
|
|
|
* - State::TYPE_NORMAL |
100
|
|
|
* - State::TYPE_FINAL |
101
|
|
|
* - State::TYPE_REGEX |
102
|
|
|
* @var string |
103
|
|
|
*/ |
104
|
|
|
protected $type; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* an array of transitions that are outgoing for this state. |
108
|
|
|
* These will be set by Transition objects (they provide the association) |
109
|
|
|
* |
110
|
|
|
* this is not a hashmap, so the order of Transitions *might* be important. |
111
|
|
|
* whenever a State is asked for it's transitions, the first transition |
112
|
|
|
* might |
113
|
|
|
* be tried first. this might have performance and configuration benefits |
114
|
|
|
* |
115
|
|
|
* @var Transition[] |
116
|
|
|
*/ |
117
|
|
|
protected $transitions; |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* The name of the state |
121
|
|
|
* |
122
|
|
|
* @var string |
123
|
|
|
*/ |
124
|
|
|
protected $name; |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* fully qualified command name for the command to be executed |
128
|
|
|
* when entering a state as part of a transition. |
129
|
|
|
* This can actually be a ',' seperated string of multiple commands that |
130
|
|
|
* will be executed as a composite. |
131
|
|
|
* |
132
|
|
|
* @var string |
133
|
|
|
*/ |
134
|
|
|
protected $command_entry_name; |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* fully qualified command name for the command to be executed |
138
|
|
|
* when exiting a state as part of a transition. |
139
|
|
|
* This can actually be a ',' seperated string of multiple commands that |
140
|
|
|
* will be executed as a composite. |
141
|
|
|
* |
142
|
|
|
* @var string |
143
|
|
|
*/ |
144
|
|
|
protected $command_exit_name; |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* the entry callable method |
148
|
|
|
* @var callable |
149
|
|
|
*/ |
150
|
|
|
protected $callable_entry; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* the exit callable method |
154
|
|
|
* @var callable |
155
|
|
|
*/ |
156
|
|
|
protected $callable_exit; |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* a description for the state |
160
|
|
|
* |
161
|
|
|
* @var string |
162
|
|
|
*/ |
163
|
|
|
protected $description; |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* |
167
|
|
|
* @param string $name |
168
|
|
|
* the name of the state (can also be a regex in format: [not-]regex:/<regex-specification-here>/) |
169
|
|
|
* @param string $type |
170
|
|
|
* the type of the state (on of self::TYPE_<*>) |
171
|
|
|
* @param $command_entry_name optional: |
172
|
|
|
* a command to be executed when a transition enters this state |
173
|
|
|
* One or more fully qualified command (sub)class name(s) to |
174
|
|
|
* execute when entering this state. |
175
|
|
|
* This can actually be a ',' seperated string of multiple |
176
|
|
|
* commands that will be executed as a composite. |
177
|
|
|
* @param $command_exit_name optional: |
178
|
|
|
* a command to be executed when a transition leaves this state |
179
|
|
|
* One or more fully qualified command (sub)class name(s) to |
180
|
|
|
* execute when exiting this state. |
181
|
|
|
* This can actually be a ',' seperated string of multiple |
182
|
|
|
* commands that will be executed as a composite. |
183
|
|
|
* @param callable $callable_entry |
184
|
|
|
* optional: a php callable to call. eg: "function(){echo 'closure called';};" |
185
|
|
|
* @param callable $callable_exit |
186
|
|
|
* optional: a php callable to call. eg: "izzum\MyClass::myStaticMethod" |
187
|
|
|
*/ |
188
|
79 |
|
public function __construct($name, $type = self::TYPE_NORMAL, $command_entry_name = self::COMMAND_EMPTY, $command_exit_name = self::COMMAND_EMPTY, $callable_entry = self::CALLABLE_NULL, $callable_exit = self::CALLABLE_NULL) |
189
|
|
|
{ |
190
|
79 |
|
$this->setName($name); |
191
|
79 |
|
$this->setType($type); |
192
|
79 |
|
$this->setEntryCommandName($command_entry_name); |
193
|
79 |
|
$this->setExitCommandName($command_exit_name); |
194
|
79 |
|
$this->setEntryCallable($callable_entry); |
|
|
|
|
195
|
79 |
|
$this->setExitCallable($callable_exit); |
|
|
|
|
196
|
79 |
|
$this->transitions = array(); |
197
|
79 |
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* get the entry callable, the callable to be called when entering this state |
201
|
|
|
* @return callable |
202
|
|
|
*/ |
203
|
25 |
|
public function getEntryCallable() |
204
|
|
|
{ |
205
|
25 |
|
return $this->callable_entry; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* set the entry callable, the callable to be called when entering this state |
210
|
|
|
* @param callable $callable |
211
|
|
|
*/ |
212
|
79 |
|
public function setEntryCallable($callable) |
213
|
|
|
{ |
214
|
79 |
|
$this->callable_entry = $callable; |
215
|
79 |
|
return $this; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* get the exit callable, the callable to be called when exiting this state |
220
|
|
|
* @return callable |
221
|
|
|
*/ |
222
|
25 |
|
public function getExitCallable() |
223
|
|
|
{ |
224
|
25 |
|
return $this->callable_exit; |
225
|
|
|
|
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* set the exit callable, the callable to be called when exiting this state |
230
|
|
|
* @param callable $callable |
231
|
|
|
*/ |
232
|
79 |
|
public function setExitCallable($callable) |
233
|
|
|
{ |
234
|
79 |
|
$this->callable_exit = $callable; |
235
|
79 |
|
return $this; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* is it an initial state |
240
|
|
|
* |
241
|
|
|
* @return boolean |
242
|
|
|
*/ |
243
|
25 |
|
public function isInitial() |
244
|
|
|
{ |
245
|
25 |
|
return $this->type === self::TYPE_INITIAL; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* is it a normal state |
250
|
|
|
* |
251
|
|
|
* @return boolean |
252
|
|
|
*/ |
253
|
5 |
|
public function isNormal() |
254
|
|
|
{ |
255
|
5 |
|
return $this->type === self::TYPE_NORMAL; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* is it a final state |
260
|
|
|
* |
261
|
|
|
* @return boolean |
262
|
|
|
*/ |
263
|
68 |
|
public function isFinal() |
264
|
|
|
{ |
265
|
68 |
|
return $this->type === self::TYPE_FINAL; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* is this state a regex type of state? |
270
|
|
|
* formats: |
271
|
|
|
* "regex:<regular-expression-here>" |
272
|
|
|
* "not-regex:<regular-expression-here>" |
273
|
|
|
* |
274
|
|
|
* @return boolean |
275
|
|
|
* @link https://php.net/manual/en/function.preg-match.php |
276
|
|
|
* @link http://regexr.com/ for trying out regular expressions |
277
|
|
|
*/ |
278
|
79 |
|
public function isRegex() |
279
|
|
|
{ |
280
|
|
|
//check the type (and check the state name for regex matches) |
281
|
79 |
|
return $this->type === self::TYPE_REGEX || $this->isNormalRegex() || $this->isNegatedRegex(); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* is this state a normal regex type of state? |
286
|
|
|
* "regex:<regular-expression-here>" |
287
|
|
|
* |
288
|
|
|
* @return boolean |
289
|
|
|
*/ |
290
|
79 |
|
public function isNormalRegex() |
291
|
|
|
{ |
292
|
79 |
|
return strpos($this->getName(), self::REGEX_PREFIX) === 0; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* is this state a negated regex type of state? |
297
|
|
|
* "not-regex:<regular-expression-here>" |
298
|
|
|
* |
299
|
|
|
* @return boolean |
300
|
|
|
*/ |
301
|
79 |
|
public function isNegatedRegex() |
302
|
|
|
{ |
303
|
79 |
|
return strpos($this, self::REGEX_PREFIX_NEGATED) === 0; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* get the state type |
308
|
|
|
* |
309
|
|
|
* @return string |
310
|
|
|
*/ |
311
|
2 |
|
public function getType() |
312
|
|
|
{ |
313
|
|
|
|
314
|
2 |
|
$this->isRegex(); |
315
|
2 |
|
return $this->type; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* set the state type |
320
|
|
|
* |
321
|
|
|
* @param string $type |
322
|
|
|
*/ |
323
|
79 |
|
protected function setType($type) |
324
|
|
|
{ |
325
|
|
|
//if a client mistakenly creates a regex State (a name of [not-]<regex:>), but with a non-regex type, |
326
|
|
|
//we will set it to a regex state. |
327
|
79 |
|
if($this->isRegex()) { |
328
|
23 |
|
$type = self::TYPE_REGEX; |
329
|
23 |
|
} |
330
|
79 |
|
$this->type = trim($type); |
331
|
79 |
|
return $this; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* add an outgoing transition from this state. |
336
|
|
|
* |
337
|
|
|
* TRICKY: this method should be package visibility only, |
338
|
|
|
* so don't use directly. it is used to set the bidirectional association |
339
|
|
|
* for State and Transition from a Transition instance on the state the transition will be allowed to |
340
|
|
|
* run from ('state from'). |
341
|
|
|
* |
342
|
|
|
* @param Transition $transition |
343
|
|
|
* @return boolan yes in case the transition was not on the State already or in case of an invalid transition |
344
|
|
|
*/ |
345
|
67 |
|
public function addTransition(Transition $transition) |
346
|
|
|
{ |
347
|
67 |
|
$output = false; |
348
|
|
|
// check all existing transitions. |
349
|
67 |
|
if (!$this->hasTransition($transition->getName()) |
350
|
67 |
|
&& $transition->getStateFrom()->getName() == $this->getName() |
351
|
67 |
|
&& !$this->isFinal() |
352
|
67 |
|
&& !$this->isRegex()) { |
353
|
67 |
|
$output = true; |
354
|
67 |
|
$this->transitions [] = $transition; |
355
|
67 |
|
} |
356
|
|
|
|
357
|
67 |
|
return $output; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* get all outgoing transitions |
362
|
|
|
* |
363
|
|
|
* @return Transition[] an array of transitions |
364
|
|
|
*/ |
365
|
20 |
|
public function getTransitions() |
366
|
|
|
{ |
367
|
|
|
// a subclass might return an ordered/prioritized array |
368
|
20 |
|
return $this->transitions; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* gets the name of this state |
373
|
|
|
*/ |
374
|
79 |
|
public function getName() |
375
|
|
|
{ |
376
|
79 |
|
return $this->name; |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* sets the name of this state |
381
|
|
|
* @param string $name |
382
|
|
|
*/ |
383
|
79 |
|
protected function setName($name) |
384
|
|
|
{ |
385
|
79 |
|
$this->name = trim($name); |
386
|
79 |
|
return $this; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* |
391
|
|
|
* @return string |
392
|
|
|
*/ |
393
|
79 |
|
public function __toString() |
394
|
|
|
{ |
395
|
79 |
|
return $this->getName(); |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* Do we have a transition from this state with a certain name? |
400
|
|
|
* |
401
|
|
|
* @param string $transition_name |
402
|
|
|
* @return boolean |
403
|
|
|
*/ |
404
|
67 |
|
public function hasTransition($transition_name) |
405
|
|
|
{ |
406
|
67 |
|
$has = false; |
407
|
67 |
|
foreach ($this->transitions as $transition) { |
408
|
52 |
|
if ($transition_name === $transition->getName()) { |
409
|
50 |
|
$has = true; |
410
|
50 |
|
break; |
411
|
|
|
} |
412
|
67 |
|
} |
413
|
67 |
|
return $has; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* An action executed every time a state is entered. |
418
|
|
|
* An entry action will not be executed for an 'initial' state. |
419
|
|
|
* |
420
|
|
|
* @param Context $context |
421
|
|
|
* @throws Exception |
422
|
|
|
*/ |
423
|
22 |
|
public function entryAction(Context $context) |
424
|
|
|
{ |
425
|
22 |
|
$command = $this->getCommand($this->getEntryCommandName(), $context); |
426
|
22 |
|
$this->execute($command); |
427
|
22 |
|
$this->callCallable($this->getEntryCallable(), $context, self::CALLABLE_ENTRY); |
428
|
21 |
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* calls a $callable if it exists, with the arguments $context->getEntity() |
432
|
|
|
* @param callable $callable |
433
|
|
|
* @param Context $context |
434
|
|
|
* @param string $type the type of callable (self::CALLABLE_ENTRY | self::CALLABLE_EXIT) |
435
|
|
|
*/ |
436
|
22 |
|
protected function callCallable($callable, Context $context, $type = 'n/a') |
437
|
|
|
{ |
438
|
22 |
|
if ($callable != self::CALLABLE_NULL){ |
439
|
9 |
|
Utils::checkCallable($callable, $type, $this, $context); |
440
|
8 |
|
call_user_func($callable, $context->getEntity()); |
441
|
8 |
|
} |
442
|
21 |
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* An action executed every time a state is exited. |
446
|
|
|
* An exit action will not be executed for a 'final' state since a machine |
447
|
|
|
* will not leave a 'final' state. |
448
|
|
|
* |
449
|
|
|
* @param Context $context |
450
|
|
|
* @throws Exception |
451
|
|
|
*/ |
452
|
22 |
|
public function exitAction(Context $context) |
453
|
|
|
{ |
454
|
22 |
|
$command = $this->getCommand($this->getExitCommandName(), $context); |
455
|
22 |
|
$this->execute($command); |
456
|
22 |
|
$this->callCallable($this->getExitCallable(), $context, self::CALLABLE_EXIT); |
457
|
21 |
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* helper method |
461
|
|
|
* |
462
|
|
|
* @param ICommand $command |
463
|
|
|
* @throws Exception |
464
|
|
|
*/ |
465
|
22 |
|
protected function execute(ICommand $command) |
466
|
|
|
{ |
467
|
|
|
try { |
468
|
22 |
|
$command->execute(); |
469
|
22 |
|
} catch(\Exception $e) { |
470
|
|
|
// command failure |
471
|
1 |
|
$e = new Exception($e->getMessage(), Exception::COMMAND_EXECUTION_FAILURE, $e); |
472
|
1 |
|
throw $e; |
473
|
|
|
} |
474
|
22 |
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* returns the associated Command for the entry/exit action. |
478
|
|
|
* the Command will be configured with the domain model via dependency injection |
479
|
|
|
* |
480
|
|
|
* @param string $command_name |
481
|
|
|
* entry or exit command name |
482
|
|
|
* @param Context $context |
483
|
|
|
* @return ICommand |
484
|
|
|
* @throws Exception |
485
|
|
|
*/ |
486
|
22 |
|
protected function getCommand($command_name, Context $context) |
487
|
|
|
{ |
488
|
22 |
|
return Utils::getCommand($command_name, $context); |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* get the transition for this state that can be triggered by an event code. |
493
|
|
|
* |
494
|
|
|
* @param string $event |
495
|
|
|
* the event code that can trigger a transition (mealy machine) |
496
|
|
|
* @return Transition[] |
497
|
|
|
*/ |
498
|
11 |
|
public function getTransitionsTriggeredByEvent($event) |
499
|
|
|
{ |
500
|
11 |
|
$output = array(); |
501
|
11 |
|
foreach ($this->getTransitions() as $transition) { |
502
|
11 |
|
if ($transition->isTriggeredBy($event)) { |
503
|
11 |
|
$output [] = $transition; |
504
|
11 |
|
} |
505
|
11 |
|
} |
506
|
11 |
|
return $output; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
/** |
510
|
|
|
* get the fully qualified command name for entry of the state |
511
|
|
|
* |
512
|
|
|
* @return string |
513
|
|
|
*/ |
514
|
24 |
|
public function getEntryCommandName() |
515
|
|
|
{ |
516
|
24 |
|
return $this->command_entry_name; |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
/** |
520
|
|
|
* get the fully qualified command name for exit of the state |
521
|
|
|
* |
522
|
|
|
* @return string |
523
|
|
|
*/ |
524
|
24 |
|
public function getExitCommandName() |
525
|
|
|
{ |
526
|
24 |
|
return $this->command_exit_name; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
/** |
530
|
|
|
* set the exit command name |
531
|
|
|
* @param string $name a fully qualified command name |
532
|
|
|
*/ |
533
|
79 |
|
public function setExitCommandName($name) |
534
|
|
|
{ |
535
|
79 |
|
$this->command_exit_name = trim($name); |
536
|
79 |
|
return $this; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
/** |
540
|
|
|
* set the entry command name |
541
|
|
|
* @param string $name a fully qualified command name |
542
|
|
|
*/ |
543
|
79 |
|
public function setEntryCommandName($name) |
544
|
|
|
{ |
545
|
79 |
|
$this->command_entry_name = trim($name); |
546
|
79 |
|
return $this; |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
/** |
550
|
|
|
* set the description of the state (for uml generation for example) |
551
|
|
|
* |
552
|
|
|
* @param string $description |
553
|
|
|
*/ |
554
|
15 |
|
public function setDescription($description) |
555
|
|
|
{ |
556
|
15 |
|
$this->description = $description; |
557
|
15 |
|
return $this; |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
/** |
561
|
|
|
* get the description for this state (if any) |
562
|
|
|
* |
563
|
|
|
* @return string |
564
|
|
|
*/ |
565
|
3 |
|
public function getDescription() |
566
|
|
|
{ |
567
|
3 |
|
return $this->description; |
568
|
|
|
} |
569
|
|
|
} |
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.