1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Workflow library. |
5
|
|
|
* |
6
|
|
|
* @package workflow |
7
|
|
|
* @author David Molineus <[email protected]> |
8
|
|
|
* @copyright 2014-2017 netzmacht David Molineus |
9
|
|
|
* @license LGPL 3.0 https://github.com/netzmacht/workflow |
10
|
|
|
* @filesource |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
declare(strict_types=1); |
14
|
|
|
|
15
|
|
|
namespace Netzmacht\Workflow\Flow; |
16
|
|
|
|
17
|
|
|
use Netzmacht\Workflow\Base; |
18
|
|
|
use Netzmacht\Workflow\Flow\Condition\Transition\AndCondition; |
19
|
|
|
use Netzmacht\Workflow\Flow\Condition\Transition\Condition; |
20
|
|
|
use Netzmacht\Workflow\Flow\Exception\ActionFailedException; |
21
|
|
|
use Netzmacht\Workflow\Security\Permission; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Class Transition handles the transition from a step to another. |
25
|
|
|
* |
26
|
|
|
* @SuppressWarnings(PHPMD.TooManyPublicMethods) |
27
|
|
|
*/ |
28
|
|
|
class Transition extends Base |
29
|
|
|
{ |
30
|
|
|
/** |
31
|
|
|
* Actions which will be executed during the transition. |
32
|
|
|
* |
33
|
|
|
* @var Action[] |
34
|
|
|
*/ |
35
|
|
|
private $actions = []; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Post actions which will be executed when new step is reached. |
39
|
|
|
* |
40
|
|
|
* @var Action[] |
41
|
|
|
*/ |
42
|
|
|
private $postActions = []; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* The step the transition is moving to. |
46
|
|
|
* |
47
|
|
|
* @var Step |
48
|
|
|
*/ |
49
|
|
|
private $stepTo; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* A pre condition which has to be passed to execute transition. |
53
|
|
|
* |
54
|
|
|
* @var AndCondition |
55
|
|
|
*/ |
56
|
|
|
private $preCondition; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* A condition which has to be passed to execute the transition. |
60
|
|
|
* |
61
|
|
|
* @var AndCondition |
62
|
|
|
*/ |
63
|
|
|
private $condition; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* A set of permission being assigned to the transition. |
67
|
|
|
* |
68
|
|
|
* @var Permission|null |
69
|
|
|
*/ |
70
|
|
|
private $permission; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* The corresponding workflow. |
74
|
|
|
* |
75
|
|
|
* @var Workflow |
76
|
|
|
*/ |
77
|
|
|
private $workflow; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Transition constructor. |
81
|
|
|
* |
82
|
|
|
* @param string $name Name of the element. |
83
|
|
|
* @param Workflow $workflow The workflow to which the transition belongs. |
84
|
|
|
* @param Step $stepTo The target step. |
85
|
|
|
* @param string $label Label of the element. |
86
|
|
|
* @param array $config Configuration values. |
87
|
|
|
*/ |
88
|
|
|
public function __construct(string $name, Workflow $workflow, Step $stepTo, string $label = '', array $config = []) |
89
|
|
|
{ |
90
|
|
|
parent::__construct($name, $label, $config); |
91
|
|
|
|
92
|
|
|
$workflow->addTransition($this); |
93
|
|
|
|
94
|
|
|
$this->workflow = $workflow; |
95
|
|
|
$this->stepTo = $stepTo; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Get the workflow. |
100
|
|
|
* |
101
|
|
|
* @return Workflow |
102
|
|
|
*/ |
103
|
|
|
public function getWorkflow(): Workflow |
104
|
|
|
{ |
105
|
|
|
return $this->workflow; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Add an action to the transition. |
110
|
|
|
* |
111
|
|
|
* @param Action $action The added action. |
112
|
|
|
* |
113
|
|
|
* @return $this |
114
|
|
|
*/ |
115
|
|
|
public function addAction(Action $action): self |
116
|
|
|
{ |
117
|
|
|
$this->actions[] = $action; |
118
|
|
|
|
119
|
|
|
return $this; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Get all actions. |
124
|
|
|
* |
125
|
|
|
* @return Action[]|iterable |
126
|
|
|
*/ |
127
|
|
|
public function getActions(): iterable |
128
|
|
|
{ |
129
|
|
|
return $this->actions; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Add an post action to the transition. |
134
|
|
|
* |
135
|
|
|
* @param Action $action The added action. |
136
|
|
|
* |
137
|
|
|
* @return $this |
138
|
|
|
*/ |
139
|
|
|
public function addPostAction(Action $action): self |
140
|
|
|
{ |
141
|
|
|
$this->postActions[] = $action; |
142
|
|
|
|
143
|
|
|
return $this; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Get all post actions. |
148
|
|
|
* |
149
|
|
|
* @return Action[]|iterable |
150
|
|
|
*/ |
151
|
|
|
public function getPostActions(): iterable |
152
|
|
|
{ |
153
|
|
|
return $this->postActions; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Get the target step. |
158
|
|
|
* |
159
|
|
|
* @return Step |
160
|
|
|
*/ |
161
|
|
|
public function getStepTo():? Step |
162
|
|
|
{ |
163
|
|
|
return $this->stepTo; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Get the condition. |
168
|
|
|
* |
169
|
|
|
* @return Condition|null |
170
|
|
|
*/ |
171
|
|
|
public function getCondition():? Condition |
172
|
|
|
{ |
173
|
|
|
return $this->condition; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Add a condition. |
178
|
|
|
* |
179
|
|
|
* @param Condition $condition The new condition. |
180
|
|
|
* |
181
|
|
|
* @return $this |
182
|
|
|
*/ |
183
|
|
|
public function addCondition(Condition $condition): self |
184
|
|
|
{ |
185
|
|
|
if (!$this->condition) { |
186
|
|
|
$this->condition = new AndCondition(); |
187
|
|
|
} |
188
|
|
|
$this->condition->addCondition($condition); |
189
|
|
|
|
190
|
|
|
return $this; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Get the precondition. |
195
|
|
|
* |
196
|
|
|
* @return Condition |
197
|
|
|
*/ |
198
|
|
|
public function getPreCondition():? Condition |
199
|
|
|
{ |
200
|
|
|
return $this->preCondition; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Add a precondition precondition. |
205
|
|
|
* |
206
|
|
|
* @param Condition $preCondition The new precondition. |
207
|
|
|
* |
208
|
|
|
* @return $this |
209
|
|
|
*/ |
210
|
|
|
public function addPreCondition(Condition $preCondition): self |
211
|
|
|
{ |
212
|
|
|
if (!$this->preCondition) { |
213
|
|
|
$this->preCondition = new AndCondition(); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
$this->preCondition->addCondition($preCondition); |
217
|
|
|
|
218
|
|
|
return $this; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Consider if user input is required. |
223
|
|
|
* |
224
|
|
|
* @param Item $item Workflow item. |
225
|
|
|
* |
226
|
|
|
* @return array |
227
|
|
|
*/ |
228
|
|
|
public function getRequiredPayloadProperties(Item $item): array |
229
|
|
|
{ |
230
|
|
|
if (!$this->actions) { |
|
|
|
|
231
|
|
|
return []; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
return array_merge( |
235
|
|
|
... array_map( |
236
|
|
|
function (Action $action) use ($item) { |
237
|
|
|
return $action->getRequiredPayloadProperties($item); |
238
|
|
|
}, |
239
|
|
|
$this->actions |
240
|
|
|
) |
241
|
|
|
); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Validate the given item and context (payload properties). |
246
|
|
|
* |
247
|
|
|
* @param Item $item Workflow item. |
248
|
|
|
* @param Context $context Transition context. |
249
|
|
|
* |
250
|
|
|
* @return bool |
251
|
|
|
*/ |
252
|
|
|
public function validate(Item $item, Context $context): bool |
253
|
|
|
{ |
254
|
|
|
$validated = true; |
255
|
|
|
|
256
|
|
|
foreach ($this->actions as $action) { |
257
|
|
|
$validated = $validated && $action->validate($item, $context); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
return $validated; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Consider if transition is allowed. |
265
|
|
|
* |
266
|
|
|
* @param Item $item The Item. |
267
|
|
|
* @param Context $context The transition context. |
268
|
|
|
* |
269
|
|
|
* @return bool |
270
|
|
|
*/ |
271
|
|
|
public function isAllowed(Item $item, Context $context): bool |
272
|
|
|
{ |
273
|
|
|
if ($this->checkPreCondition($item, $context)) { |
274
|
|
|
return $this->checkCondition($item, $context); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
return false; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Consider if transition is available. |
282
|
|
|
* |
283
|
|
|
* If a transition can be available but it is not allowed depending on the user input. |
284
|
|
|
* |
285
|
|
|
* @param Item $item The Item. |
286
|
|
|
* @param Context $context The transition context. |
287
|
|
|
* |
288
|
|
|
* @return bool |
289
|
|
|
*/ |
290
|
|
|
public function isAvailable(Item $item, Context $context): bool |
291
|
|
|
{ |
292
|
|
|
if ($this->getRequiredPayloadProperties($item)) { |
293
|
|
|
return $this->checkPreCondition($item, $context); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
return $this->isAllowed($item, $context); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Check the precondition. |
301
|
|
|
* |
302
|
|
|
* @param Item $item The Item. |
303
|
|
|
* @param Context $context The transition context. |
304
|
|
|
* |
305
|
|
|
* @return bool |
306
|
|
|
*/ |
307
|
|
|
public function checkPreCondition(Item $item, Context $context): bool |
308
|
|
|
{ |
309
|
|
|
return $this->performConditionCheck($this->preCondition, $item, $context); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Check the condition. |
314
|
|
|
* |
315
|
|
|
* @param Item $item The Item. |
316
|
|
|
* @param Context $context The transition context. |
317
|
|
|
* |
318
|
|
|
* @return bool |
319
|
|
|
*/ |
320
|
|
|
public function checkCondition(Item $item, Context $context): bool |
321
|
|
|
{ |
322
|
|
|
return $this->performConditionCheck($this->condition, $item, $context); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Set a permission to the transition. |
327
|
|
|
* |
328
|
|
|
* @param Permission $permission Permission being assigned. |
329
|
|
|
* |
330
|
|
|
* @return $this |
331
|
|
|
*/ |
332
|
|
|
public function setPermission(Permission $permission): self |
333
|
|
|
{ |
334
|
|
|
$this->permission = $permission; |
335
|
|
|
|
336
|
|
|
return $this; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Consider if permission is assigned to transition. |
341
|
|
|
* |
342
|
|
|
* @param Permission $permission Permission being check. |
343
|
|
|
* |
344
|
|
|
* @return bool |
345
|
|
|
*/ |
346
|
|
|
public function hasPermission(Permission $permission): bool |
347
|
|
|
{ |
348
|
|
|
if ($this->permission) { |
349
|
|
|
return $this->permission->equals($permission); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
return false; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Get assigned permission. Returns null if no transition is set. |
357
|
|
|
* |
358
|
|
|
* @return Permission|null |
359
|
|
|
*/ |
360
|
|
|
public function getPermission():? Permission |
361
|
|
|
{ |
362
|
|
|
return $this->permission; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Execute all actions. |
367
|
|
|
* |
368
|
|
|
* @param Item $item The workflow item. |
369
|
|
|
* @param Context $context The transition context. |
370
|
|
|
* |
371
|
|
|
* @return bool |
372
|
|
|
*/ |
373
|
|
|
public function executeActions(Item $item, Context $context): bool |
374
|
|
|
{ |
375
|
|
|
return $this->doExecuteActions($item, $context, $this->actions); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Execute all actions. |
380
|
|
|
* |
381
|
|
|
* @param Item $item The workflow item. |
382
|
|
|
* @param Context $context The transition context. |
383
|
|
|
* |
384
|
|
|
* @return bool |
385
|
|
|
*/ |
386
|
|
|
public function executePostActions(Item $item, Context $context): bool |
387
|
|
|
{ |
388
|
|
|
return $this->doExecuteActions($item, $context, $this->postActions); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Perform condition check. |
393
|
|
|
* |
394
|
|
|
* @param Condition|null $condition Condition to be checked. |
395
|
|
|
* @param Item $item Workflow item. |
396
|
|
|
* @param Context $context Condition context. |
397
|
|
|
* |
398
|
|
|
* @return bool |
399
|
|
|
*/ |
400
|
|
|
private function performConditionCheck($condition, $item, $context): bool |
401
|
|
|
{ |
402
|
|
|
if (!$condition) { |
403
|
|
|
return true; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
return $condition->match($this, $item, $context); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Execute the actions. |
411
|
|
|
* |
412
|
|
|
* @param Item $item Workflow item. |
413
|
|
|
* @param Context $context Condition context. |
414
|
|
|
* @param Action[] $actions Action to execute. |
415
|
|
|
* |
416
|
|
|
* @return bool |
417
|
|
|
*/ |
418
|
|
|
private function doExecuteActions(Item $item, Context $context, $actions): bool |
419
|
|
|
{ |
420
|
|
|
$success = $this->isAllowed($item, $context); |
421
|
|
|
|
422
|
|
|
if ($success) { |
423
|
|
|
try { |
424
|
|
|
foreach ($actions as $action) { |
425
|
|
|
$action->transit($this, $item, $context); |
426
|
|
|
} |
427
|
|
|
} catch (ActionFailedException $e) { |
428
|
|
|
$params = ['exception' => $e->getMessage()]; |
429
|
|
|
$context->addError('transition.action.failed', $params); |
430
|
|
|
|
431
|
|
|
return false; |
432
|
|
|
} |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
return $success; |
436
|
|
|
} |
437
|
|
|
} |
438
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.