GetActivityInstanceCmd::execute()   F
last analyzed

Complexity

Conditions 20
Paths 277

Size

Total Lines 141
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 82
dl 0
loc 141
rs 2.3708
c 0
b 0
f 0
cc 20
nc 277
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Jabe\Impl\Cmd;
4
5
use Jabe\ProcessEngineException;
6
use Jabe\Impl\Interceptor\{
7
    CommandInterface,
8
    CommandContext
9
};
10
use Jabe\Impl\Persistence\Entity\{
11
    ActivityInstanceImpl,
12
    ExecutionEntity,
13
    IncidentEntity,
14
    TransitionInstanceImpl
15
};
16
use Jabe\Impl\Pvm\Process\{
17
    ActivityImpl,
18
    ScopeImpl
19
};
20
use Jabe\Impl\Pvm\Runtime\{
21
    CompensationBehavior,
22
    LegacyBehavior,
23
    PvmExecutionImpl
24
};
25
use Jabe\Impl\Util\{
26
    CollectionUtil,
27
    EnsureUtil
28
};
29
use Jabe\Runtime\{
30
    ActivityInstanceInterface,
31
    IncidentInterface
32
};
33
34
class GetActivityInstanceCmd implements CommandInterface
35
{
36
    protected $processInstanceId;
37
38
    public function __construct(string $processInstanceId)
39
    {
40
        $this->processInstanceId = $processInstanceId;
41
    }
42
43
    public function execute(CommandContext $commandContext)
44
    {
45
        EnsureUtil::ensureNotNull("processInstanceId", "processInstanceId", $this->processInstanceId);
46
        $executionList = $this->loadProcessInstance($this->processInstanceId, $commandContext);
47
48
        if (empty($executionList)) {
49
            return null;
50
        }
51
52
        $this->checkGetActivityInstance($this->processInstanceId, $commandContext);
53
54
        $nonEventScopeExecutions = $this->filterNonEventScopeExecutions($executionList);
55
        $leaves = $this->filterLeaves($nonEventScopeExecutions);
56
        // Leaves must be ordered in a predictable way (e.g. by ID)
57
        // in order to return a stable execution tree with every repeated invocation of this command.
58
        // For legacy process instances, there may miss scope executions for activities that are now a scope.
59
        // In this situation, there may be multiple scope candidates for the same instance id; which one
60
        // can depend on the order the leaves are iterated.
61
        $this->orderById($leaves);
62
63
        $processInstance = $this->filterProcessInstance($executionList);
64
65
        if ($processInstance->isEnded()) {
66
            return null;
67
        }
68
69
        $incidents = $this->groupIncidentIdsByExecutionId($commandContext);
70
71
        // create act instance for process instance
72
        $processActInst = $this->createActivityInstance(
73
            $processInstance,
74
            $processInstance->getProcessDefinition(),
75
            $this->processInstanceId,
76
            null,
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $parentActivityInstanceId of Jabe\Impl\Cmd\GetActivit...reateActivityInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
            /** @scrutinizer ignore-type */ null,
Loading history...
77
            $incidents
78
        );
79
80
        $activityInstances = [];
81
        $activityInstances[$this->processInstanceId] = $processActInst;
82
83
        $transitionInstances = [];
84
85
        foreach ($leaves as $leaf) {
86
            // skip leafs without activity, e.g. if only the process instance exists after cancellation
87
            // it will not have an activity set
88
            if ($leaf->getActivity() === null) {
89
                continue;
90
            }
91
92
            $activityExecutionMapping = $leaf->createActivityExecutionMapping();
93
            $scopeInstancesToCreate = $activityExecutionMapping;
94
95
            // create an activity/transition instance for each leaf that executes a non-scope activity
96
            // and does not throw compensation
97
            if ($leaf->getActivityInstanceId() !== null) {
98
                if (!CompensationBehavior::isCompensationThrowing($leaf) || LegacyBehavior::isCompensationThrowing($leaf, $activityExecutionMapping)) {
99
                    $parentActivityInstanceId = null;
100
                    foreach ($activityExecutionMapping as $pair) {
101
                        if ($pair[0] == $leaf->getActivity()->getFlowScope()) {
102
                            $parentActivityInstanceId = $pair[1]->getParentActivityInstanceId();
103
                            break;
104
                        }
105
                    }
106
107
                    $leafInstance = $this->createActivityInstance(
108
                        $leaf,
109
                        $leaf->getActivity(),
110
                        $leaf->getActivityInstanceId(),
111
                        $parentActivityInstanceId,
112
                        $incidents
113
                    );
114
                    $activityInstances[$leafInstance->getId()] = $leafInstance;
115
116
                    $actToRemove = $leaf->getActivity();
117
                    foreach ($scopeInstancesToCreate as $key => $pair) {
118
                        if ($pair[0] == $actToRemove) {
119
                            unset($scopeInstancesToCreate[$key]);
120
                            break;
121
                        }
122
                    }
123
                }
124
            } else {
125
                $transitionInstance = $this->createTransitionInstance($leaf, $incidents);
126
                $transitionInstances[$transitionInstance->getId()] = $transitionInstance;
127
                $actToRemove = $leaf->getActivity();
128
                foreach ($scopeInstancesToCreate as $key => $pair) {
129
                    if ($pair[0] == $actToRemove) {
130
                        unset($scopeInstancesToCreate[$key]);
131
                        break;
132
                    }
133
                }
134
            }
135
136
            LegacyBehavior::removeLegacyNonScopesFromMapping($scopeInstancesToCreate);
137
            $actToRemove = $leaf->getProcessDefinition();
138
            foreach ($scopeInstancesToCreate as $key => $pair) {
139
                if ($pair[0] == $actToRemove) {
140
                    unset($scopeInstancesToCreate[$key]);
141
                    break;
142
                }
143
            }
144
145
            // create an activity instance for each scope (including compensation throwing executions)
146
            foreach ($scopeInstancesToCreate as $pair) {
147
                $scope = $pair[0];
148
                $scopeExecution = $pair[1];
149
150
                $activityInstanceId = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $activityInstanceId is dead and can be removed.
Loading history...
151
                $parentActivityInstanceId = null;
152
153
                $activityInstanceId = $scopeExecution->getParentActivityInstanceId();
154
155
                foreach ($activityExecutionMapping as $pair2) {
156
                    if ($pair2[0] == $scope->getFlowScope()) {
157
                        $parentActivityInstanceId = $pair2[1]->getParentActivityInstanceId();
158
                        break;
159
                    }
160
                }
161
162
                if (array_key_exists($activityInstanceId, $activityInstances)) {
163
                    continue;
164
                } else {
165
                    // regardless of the tree structure (compacted or not), the scope's activity instance id
166
                    // is the activity instance id of the parent execution and the parent activity instance id
167
                    // of that is the actual parent activity instance id
168
                    $scopeInstance = $this->createActivityInstance(
169
                        $scopeExecution,
170
                        $scope,
171
                        $activityInstanceId,
172
                        $parentActivityInstanceId,
173
                        $incidents
174
                    );
175
                    $activityInstances[$activityInstanceId] = $scopeInstance;
176
                }
177
            }
178
        }
179
180
        LegacyBehavior::repairParentRelationships(array_values($activityInstances), $this->processInstanceId);
181
        $this->populateChildInstances($activityInstances, $transitionInstances);
182
183
        return $processActInst;
184
    }
185
186
    protected function checkGetActivityInstance(string $processInstanceId, CommandContext $commandContext): void
187
    {
188
        foreach ($commandContext->getProcessEngineConfiguration()->getCommandCheckers() as $checker) {
0 ignored issues
show
Bug introduced by
The method getCommandCheckers() does not exist on Jabe\Impl\Cfg\ProcessEngineConfigurationImpl. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

188
        foreach ($commandContext->getProcessEngineConfiguration()->/** @scrutinizer ignore-call */ getCommandCheckers() as $checker) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
189
            $checker->checkReadProcessInstance($processInstanceId);
190
        }
191
    }
192
193
    protected function orderById(array &$leaves): void
194
    {
195
        usort($leaves, function (ExecutionEntity $o1, ExecutionEntity $o2) {
196
            return ($o1->getId() < $o2->getId()) ? -1 : 1;
197
        });
198
    }
199
200
    protected function createActivityInstance(
201
        PvmExecutionImpl $scopeExecution,
202
        ScopeImpl $scope,
203
        string $activityInstanceId,
204
        string $parentActivityInstanceId,
205
        array $incidentsByExecution
206
    ): ActivityInstanceImpl {
207
        $actInst = new ActivityInstanceImpl();
208
209
        $actInst->setId($activityInstanceId);
210
        $actInst->setParentActivityInstanceId($parentActivityInstanceId);
211
        $actInst->setProcessInstanceId($scopeExecution->getProcessInstanceId());
212
        $actInst->setProcessDefinitionId($scopeExecution->getProcessDefinitionId());
213
        $actInst->setBusinessKey($scopeExecution->getBusinessKey());
0 ignored issues
show
Bug introduced by
It seems like $scopeExecution->getBusinessKey() can also be of type null; however, parameter $businessKey of Jabe\Impl\Persistence\En...eImpl::setBusinessKey() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
        $actInst->setBusinessKey(/** @scrutinizer ignore-type */ $scopeExecution->getBusinessKey());
Loading history...
214
        $actInst->setActivityId($scope->getId());
215
216
        $name = $scope->getName();
217
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
218
            $name = $scope->getProperty("name");
219
        }
220
        $actInst->setActivityName($name);
221
222
        if ($scope->getId() == $scopeExecution->getProcessDefinition()->getId()) {
223
            $actInst->setActivityType("processDefinition");
224
        } else {
225
            $actInst->setActivityType($scope->getProperty("type"));
0 ignored issues
show
Bug introduced by
It seems like $scope->getProperty('type') can also be of type null; however, parameter $activityType of Jabe\Impl\Persistence\En...Impl::setActivityType() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

225
            $actInst->setActivityType(/** @scrutinizer ignore-type */ $scope->getProperty("type"));
Loading history...
226
        }
227
228
        $executionIds = [];
229
        $incidentIds = [];
230
        $incidents = [];
231
232
        $executionIds[] = $scopeExecution->getId();
233
234
        $executionActivity = $scopeExecution->getActivity();
235
236
        // do not collect incidents if scopeExecution is a compacted subtree
237
        // and we currently create the scope activity instance
238
        if ($executionActivity === null || $executionActivity == $scope) {
239
            $incidentIds = array_merge($incidentIds, $this->getIncidentIds($incidentsByExecution, $scopeExecution));
240
            $incidents = array_merge($incidents, $this->getIncidents($incidentsByExecution, $scopeExecution));
241
        }
242
243
        foreach ($scopeExecution->getNonEventScopeExecutions() as $childExecution) {
244
            // add all concurrent children that are not in an activity
245
            if ($childExecution->isConcurrent() && $childExecution->getActivityId() === null) {
246
                $executionIds[] = $childExecution->getId();
247
                $incidentIds = array_merge($incidentIds, $this->getIncidentIds($incidentsByExecution, $childExecution));
248
                $incidents = array_merge($incidents, $this->getIncidents($incidentsByExecution, $childExecution));
249
            }
250
        }
251
252
        $actInst->setExecutionIds($executionIds);
253
        $actInst->setIncidentIds($incidentIds);
254
        $actInst->setIncidents($incidents);
255
256
        return $actInst;
257
    }
258
259
    protected function createTransitionInstance(
260
        PvmExecutionImpl $execution,
261
        array $incidentsByExecution
262
    ): TransitionInstanceImpl {
263
        $transitionInstance = new TransitionInstanceImpl();
264
265
        // can use execution id as persistent ID for transition as an execution
266
        // can execute as most one transition at a time.
267
        $transitionInstance->setId($execution->getId());
268
        $transitionInstance->setParentActivityInstanceId($execution->getParentActivityInstanceId());
0 ignored issues
show
Bug introduced by
It seems like $execution->getParentActivityInstanceId() can also be of type null; however, parameter $parentActivityInstanceId of Jabe\Impl\Persistence\En...entActivityInstanceId() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

268
        $transitionInstance->setParentActivityInstanceId(/** @scrutinizer ignore-type */ $execution->getParentActivityInstanceId());
Loading history...
269
        $transitionInstance->setProcessInstanceId($execution->getProcessInstanceId());
270
        $transitionInstance->setProcessDefinitionId($execution->getProcessDefinitionId());
271
        $transitionInstance->setExecutionId($execution->getId());
272
        $transitionInstance->setActivityId($execution->getActivityId());
0 ignored issues
show
Bug introduced by
It seems like $execution->getActivityId() can also be of type null; however, parameter $activityId of Jabe\Impl\Persistence\En...ceImpl::setActivityId() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

272
        $transitionInstance->setActivityId(/** @scrutinizer ignore-type */ $execution->getActivityId());
Loading history...
273
274
        $activity = $execution->getActivity();
275
        if ($activity !== null) {
276
            $name = $activity->getName();
277
            if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
278
                $name = $activity->getProperty("name");
279
            }
280
            $transitionInstance->setActivityName($name);
281
            $transitionInstance->setActivityType($activity->getProperty("type"));
0 ignored issues
show
Bug introduced by
It seems like $activity->getProperty('type') can also be of type null; however, parameter $activityType of Jabe\Impl\Persistence\En...Impl::setActivityType() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

281
            $transitionInstance->setActivityType(/** @scrutinizer ignore-type */ $activity->getProperty("type"));
Loading history...
282
        }
283
284
        $incidentIdList = $this->getIncidentIds($incidentsByExecution, $execution);
285
        $incidents = $this->getIncidents($incidentsByExecution, $execution);
286
        $transitionInstance->setIncidentIds($incidentIdList);
287
        $transitionInstance->setIncidents($incidents);
288
289
        return $transitionInstance;
290
    }
291
292
    protected function populateChildInstances(
293
        array $activityInstances,
294
        array $transitionInstances
295
    ): void {
296
        $childActivityInstances = [];
297
        $childTransitionInstances = [];
298
299
        foreach (array_values($activityInstances) as $instance) {
300
            if ($instance->getParentActivityInstanceId() !== null) {
301
                $key = $instance->getParentActivityInstanceId();
302
                $parentInstance = null;
303
                if (array_key_exists($key, $activityInstances)) {
304
                    $parentInstance = $activityInstances[$key];
305
                }
306
                if ($parentInstance === null) {
307
                    throw new ProcessEngineException("No parent activity instance with id " . $instance->getParentActivityInstanceId() . " generated");
308
                }
309
                $this->putListElement($childActivityInstances, $parentInstance, $instance);
310
            }
311
        }
312
313
        foreach (array_values($transitionInstances) as $instance) {
314
            if ($instance->getParentActivityInstanceId() !== null) {
315
                $key = $instance->getParentActivityInstanceId();
316
                $parentInstance = null;
317
                if (array_key_exists($key, $activityInstances)) {
318
                    $parentInstance = $activityInstances[$key];
319
                }
320
                if ($parentInstance === null) {
321
                    throw new ProcessEngineException("No parent activity instance with id " . $instance->getParentActivityInstanceId() . " generated");
322
                }
323
                $this->putListElement($childTransitionInstances, $parentInstance, $instance);
324
            }
325
        }
326
327
        foreach ($childActivityInstances as $pair) {
328
            $instance = $pair[0];
329
            $childInstances = $pair[1];
330
            if (!empty($childInstances)) {
331
                $instance->setChildActivityInstances($childInstances);
332
            }
333
        }
334
335
        foreach ($childTransitionInstances as $pair) {
336
            $instance = $pair[0];
337
            $childInstances = $pair[1];
338
            if (!empty($childTransitionInstances)) {
339
                $instance->setChildTransitionInstances($childInstances);
340
            }
341
        }
342
    }
343
344
    protected function putListElement(array &$mapOfLists, $key, $listElement): void
345
    {
346
        $exists = false;
347
        foreach ($mapOfLists as $idx => $pair) {
348
            if ($pair[0] == $key) {
349
                $exists = true;
350
                $mapOfLists[$idx] = [$key, array_merge($pair[1], [$listElement])];
351
                break;
352
            }
353
        }
354
        if (!$exists) {
355
            $mapOfLists[] = [$key, [$listElement]];
356
        }
357
    }
358
359
    protected function filterProcessInstance(array $executionList): ExecutionEntity
360
    {
361
        foreach ($executionList as $execution) {
362
            if ($execution->isProcessInstanceExecution()) {
363
                return $execution;
364
            }
365
        }
366
        throw new ProcessEngineException("Could not determine process instance execution");
367
    }
368
369
    protected function filterLeaves(array $executionList): array
370
    {
371
        $leaves = [];
372
        foreach ($executionList as $execution) {
373
            // although executions executing throwing compensation events are not leaves in the tree,
374
            // they are treated as leaves since their child executions are logical children of their parent scope execution
375
            if (empty($execution->getNonEventScopeExecutions()) || CompensationBehavior::isCompensationThrowing($execution)) {
376
                $leaves[] = $execution;
377
            }
378
        }
379
        return $leaves;
380
    }
381
382
    protected function filterNonEventScopeExecutions(array $executionList): array
383
    {
384
        $nonEventScopeExecutions = [];
385
        foreach ($executionList as $execution) {
386
            if (!$execution->isEventScope()) {
387
                $nonEventScopeExecutions[] = $execution;
388
            }
389
        }
390
        return $nonEventScopeExecutions;
391
    }
392
393
    protected function loadProcessInstance(string $processInstanceId, CommandContext $commandContext): array
394
    {
395
        $result = null;
396
397
        // first try to load from cache
398
        // check whether the process instance is already (partially) loaded in command context
399
        $cachedExecutions = $commandContext->getDbEntityManager()->getCachedEntitiesByType(ExecutionEntity::class);
400
        foreach ($cachedExecutions as $executionEntity) {
401
            if ($this->processInstanceId == $executionEntity->getProcessInstanceId()) {
402
                // found one execution from process instance
403
                $result = [];
404
                $processInstance = $executionEntity->getProcessInstance();
405
                // add process instance
406
                $result[] = $processInstance;
407
                $this->loadChildExecutionsFromCache($processInstance, $result);
408
                break;
409
            }
410
        }
411
412
        if (empty($result)) {
413
            // if the process instance could not be found in cache, load from database
414
            $result = $this->loadFromDb($processInstanceId, $commandContext);
415
        }
416
417
        return $result;
418
    }
419
420
    protected function loadFromDb(string $processInstanceId, CommandContext $commandContext): array
421
    {
422
        $executions = $commandContext->getExecutionManager()->findExecutionsByProcessInstanceId($processInstanceId);
423
        $processInstance = $commandContext->getExecutionManager()->findExecutionById($processInstanceId);
424
425
        // initialize parent/child sets
426
        if ($processInstance !== null) {
427
            $processInstance->restoreProcessInstance($executions, null, null, null, null, null, null);
428
        }
429
430
        return $executions;
431
    }
432
433
    /**
434
     * Loads all executions that are part of this process instance tree from the dbSqlSession cache.
435
     * (optionally querying the db if a child is not already loaded.
436
     *
437
     * @param execution the current root execution (already contained in childExecutions)
438
     * @param childExecutions the list in which all child executions should be collected
439
     */
440
    protected function loadChildExecutionsFromCache(ExecutionEntity $execution, array &$childExecutions): void
441
    {
442
        $childrenOfThisExecution = $execution->getExecutions();
443
        if (!empty($childrenOfThisExecution)) {
444
            $childExecutions = array_merge($childExecutions, $childrenOfThisExecution);
445
            foreach ($childrenOfThisExecution as $child) {
446
                $this->loadChildExecutionsFromCache($child, $childExecutions);
447
            }
448
        }
449
    }
450
451
    protected function groupIncidentIdsByExecutionId(CommandContext $commandContext): array
452
    {
453
        $incidents = $commandContext->getIncidentManager()->findIncidentsByProcessInstance($this->processInstanceId);
454
        $result = [];
455
        foreach ($incidents as $incidentEntity) {
456
            CollectionUtil::addToMapOfLists($result, $incidentEntity->getExecutionId(), $incidentEntity);
457
        }
458
        return $result;
459
    }
460
461
    protected function getIncidentIds(
462
        array $incidents,
463
        PvmExecutionImpl $execution
464
    ): array {
465
        $incidentIds = [];
466
        $key = $execution->getId();
467
        if (array_key_exists($key, $incidents)) {
468
            $incidentList = $incidents[$key];
469
        }
470
        if (!empty($incidentList)) {
471
            foreach ($incidentList as $incident) {
472
                $incidentIds[] = $incident->getId();
473
            }
474
            return $incidentIds;
475
        } else {
476
            return [];
477
        }
478
    }
479
480
    protected function getIncidents(
481
        array $incidents,
482
        PvmExecutionImpl $execution
483
    ): array {
484
        $id = $execution->getId();
485
        if (array_key_exists($id, $incidents)) {
486
            return $incidents[$id];
487
        }
488
        return [];
489
    }
490
}
491