php$4 ➔ createActivityExecutionMapping()   D
last analyzed

Complexity

Conditions 16

Size

Total Lines 183

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 183
rs 4.4532
c 0
b 0
f 0
cc 16

6 Methods

Rating   Name   Duplication   Size   Complexity  
A PvmExecutionImpl.php$3 ➔ isFulfilled() 0 10 4
A PvmExecutionImpl.php$4 ➔ __construct() 0 4 1
A PvmExecutionImpl.php$3 ➔ __construct() 0 3 1
A PvmExecutionImpl.php$2 ➔ isFulfilled() 0 10 4
A PvmExecutionImpl.php$2 ➔ __construct() 0 3 1
B PvmExecutionImpl.php$4 ➔ visit() 0 21 7

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\Pvm\Runtime;
4
5
use Jabe\ProcessEngineException;
6
use Jabe\Impl\ProcessEngineLogger;
7
use Jabe\Impl\Bpmn\Helper\BpmnProperties;
8
use Jabe\Impl\Context\Context;
9
use Jabe\Impl\Core\Instance\CoreExecution;
10
use Jabe\Impl\Core\Variable\Event\VariableEvent;
11
use Jabe\Impl\Core\Variable\Scope\AbstractVariableScope;
12
use Jabe\Impl\Form\FormPropertyHelper;
13
use Jabe\Impl\History\Event\{
14
    HistoryEvent,
15
    HistoryEventProcessor,
16
    HistoryEventCreator,
17
    HistoryEventTypes
18
};
19
use Jabe\Impl\History\Producer\HistoryEventProducerInterface;
20
use Jabe\Impl\Incident\{
21
    IncidentContext,
22
    IncidentHandlerInterface,
23
    IncidentHandling
24
};
25
use Jabe\Impl\Persistence\Entity\{
26
    DelayedVariableEvent,
27
    IncidentEntity
28
};
29
use Jabe\Impl\Pvm\{
30
    PvmActivityInterface,
31
    PvmException,
32
    PvmExecutionInterface,
33
    PvmLogger,
34
    PvmProcessDefinitionInterface,
35
    PvmProcessInstanceInterface,
36
    PvmScopeInterface,
37
    PvmTransitionInterface
38
};
39
use Jabe\Impl\Pvm\Delegate\{
40
    ActivityExecutionInterface,
41
    CompositeActivityBehaviorInterface,
42
    ModificationObserverBehaviorInterface,
43
    SignallableActivityBehaviorInterface
44
};
45
use Jabe\Impl\Pvm\Process\{
46
    ActivityImpl,
47
    ActivityStartBehavior,
48
    ProcessDefinitionImpl,
49
    ScopeImpl,
50
    TransitionImpl
51
};
52
use Jabe\Impl\Pvm\Runtime\Operation\PvmAtomicOperationInterface;
53
use Jabe\Impl\Tree\{
54
    ExecutionWalker,
55
    FlowScopeWalker,
56
    LeafActivityInstanceExecutionCollector,
57
    ReferenceWalker,
58
    ScopeCollector,
59
    ScopeExecutionCollector,
60
    TreeVisitorInterface,
61
    WalkConditionInterface
62
};
63
use Jabe\Impl\Util\EnsureUtil;
64
use Jabe\Runtime\IncidentInterface;
65
use Jabe\Variable\VariableMapInterface;
66
67
abstract class PvmExecutionImpl extends CoreExecution implements ActivityExecutionInterface, PvmProcessInstanceInterface
68
{
69
    //private static final PvmLogger LOG = ProcessEngineLogger.PVM_LOGGER;
70
71
    protected $processDefinition;
72
73
    protected $scopeInstantiationContext;
74
75
    protected $ignoreAsync = false;
76
77
    /**
78
     * true for process instances in the initial phase. Currently
79
     * this controls that historic variable updates created during this phase receive
80
     * the <code>initial</code> flag (see HistoricVariableUpdateEventEntity#isInitial).
81
     */
82
    protected $isStarting = false;
83
84
    // current position /////////////////////////////////////////////////////////
85
86
    /**
87
     * current activity
88
     */
89
    protected $activity;
90
91
    /**
92
     * the activity which is to be started next
93
     */
94
    protected $nextActivity;
95
96
    /**
97
     * the transition that is currently being taken
98
     */
99
    protected $transition;
100
101
    /**
102
     * A list of outgoing transitions from the current activity
103
     * that are going to be taken
104
     */
105
    protected $transitionsToTake = null;
106
107
    /**
108
     * the unique id of the current activity instance
109
     */
110
    protected $activityInstanceId;
111
112
    /**
113
     * the id of a case associated with this execution
114
     */
115
    protected $caseInstanceId;
116
117
    protected $replacedBy;
118
119
    // cascade deletion ////////////////////////////////////////////////////////
120
121
    protected $deleteRoot;
122
    protected $deleteReason;
123
    protected $externallyTerminated;
124
125
    //state/type of execution //////////////////////////////////////////////////
126
127
    /**
128
     * indicates if this execution represents an active path of execution.
129
     * Executions are made inactive in the following situations:
130
     * <ul>
131
     * <li>an execution enters a nested scope</li>
132
     * <li>an execution is split up into multiple concurrent executions, then the parent is made inactive.</li>
133
     * <li>an execution has arrived in a parallel gateway or join and that join has not yet activated/fired.</li>
134
     * <li>an execution is ended.</li>
135
     * </ul>
136
     */
137
    protected $isActive = true;
138
    protected $isScope = true;
139
    protected $isConcurrent = false;
140
    protected $isEnded = false;
141
    protected $isEventScope = false;
142
    protected $isRemoved = false;
143
144
    /**
145
     * transient; used for process instance modification to preserve a scope from getting deleted
146
     */
147
    protected $preserveScope = false;
148
149
    /**
150
     * marks the current activity instance
151
     */
152
    protected $activityInstanceState;
153
154
    protected $activityInstanceEndListenersFailed = false;
155
156
    // sequence counter ////////////////////////////////////////////////////////
157
    protected $sequenceCounter = 0;
158
159
    public function __construct()
160
    {
161
        $this->activityInstanceState = ActivityInstanceState::default()->getStateCode();
162
    }
163
164
    // API ////////////////////////////////////////////////
165
166
    /**
167
     * creates a new execution. properties processDefinition, processInstance and activity will be initialized.
168
     */
169
    abstract public function createExecution(): PvmExecutionImpl;
170
171
    public function createSubProcessInstance(PvmProcessDefinitionInterface $processDefinition, ?string $businessKey = null, ?string $caseInstanceId = null): PvmExecutionImpl
172
    {
173
        $subProcessInstance = $this->newExecution();
174
175
        // manage bidirectional super-subprocess relation
176
        $subProcessInstance->setSuperExecution($this);
177
        $this->setSubProcessInstance($subProcessInstance);
178
179
        // Initialize the new execution
180
        $subProcessInstance->setProcessDefinition($processDefinition);
181
        $subProcessInstance->setProcessInstance($subProcessInstance);
182
        $subProcessInstance->setActivity($processDefinition->getInitial());
183
184
        if ($businessKey !== null) {
185
            $subProcessInstance->setBusinessKey($businessKey);
186
        }
187
188
        /*if (caseInstanceId !== null) {
189
            subProcessInstance->setCaseInstanceId(caseInstanceId);
190
        }*/
191
192
        return $subProcessInstance;
193
    }
194
195
    abstract protected function newExecution(): PvmExecutionImpl;
196
197
    // sub case instance
198
199
    /*abstract public function CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition);
200
201
    @Override
202
    abstract public function CmmnExecution createSubCaseInstance(CmmnCaseDefinition caseDefinition, String businessKey);*/
203
204
    abstract public function initialize(): void;
205
206
    abstract public function initializeTimerDeclarations(): void;
207
208
    public function executeIoMapping(): void
209
    {
210
        // execute Input Mappings (if they exist).
211
        $currentScope = $this->getScopeActivity();
212
        if ($currentScope != $currentScope->getProcessDefinition()) {
213
            $currentActivity = $currentScope;
214
            if ($currentActivity !== null && $currentActivity->getIoMapping() !== null && !$this->skipIoMapping) {
215
                $currentActivity->getIoMapping()->executeInputParameters($this);
216
            }
217
        }
218
    }
219
220
    public function startWithFormProperties(VariableMapInterface $formProperties): void
221
    {
222
        $this->start(null, $formProperties);
223
    }
224
225
    public function start(?array $variables = [], ?VariableMapInterface $formProperties = null): void
226
    {
227
        $this->initialize();
228
229
        $this->fireHistoricProcessStartEvent();
230
231
        if (!empty($variables)) {
232
            $this->setVariables($variables);
233
        }
234
235
        if ($formProperties !== null) {
236
            FormPropertyHelper::initFormPropertiesOnScope($formProperties, $this);
237
        }
238
239
        $this->initializeTimerDeclarations();
240
241
        $this->performOperation(AtomicOperation::processStart());
242
    }
243
244
    /**
245
     * perform starting behavior but don't execute the initial activity
246
     *
247
     * @param variables the variables which are used for the start
248
     */
249
    public function startWithoutExecuting(array $variables): void
250
    {
251
        $this->initialize();
252
253
        $this->fireHistoricProcessStartEvent();
254
255
        $this->setActivityInstanceId($this->getId());
256
        $this->setVariables($variables);
257
258
        $this->initializeTimerDeclarations();
259
260
        $this->performOperation(AtomicOperation::fireProcessStart());
261
262
        $this->setActivity(null);
263
    }
264
265
    abstract public function fireHistoricProcessStartEvent(): void;
266
267
    public function destroy(): void
268
    {
269
        //LOG.destroying(this);
270
        $this->setScope(false);
271
    }
272
273
    public function removeAllTasks(): void
274
    {
275
    }
276
277
    protected function removeEventScopes(): void
278
    {
279
        $childExecutions = $this->getEventScopeExecutions();
280
        foreach ($childExecutions as $childExecution) {
281
            //LOG.removingEventScope(childExecution);
282
            $childExecution->destroy();
283
            $childExecution->remove();
284
        }
285
    }
286
287
    public function clearScope(string $reason, bool $skipCustomListeners, bool $skipIoMappings, bool $externallyTerminated): void
288
    {
289
        $this->skipCustomListeners = $skipCustomListeners;
290
        $this->skipIoMapping = $skipIoMappings;
291
292
        if ($this->getSubProcessInstance() !== null) {
293
            $this->getSubProcessInstance()->deleteCascade($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated, false);
294
        }
295
296
        // remove all child executions and sub process instances:
297
        $executions = $this->getNonEventScopeExecutions();
298
        foreach ($executions as $childExecution) {
299
            if ($childExecution->getSubProcessInstance() !== null) {
300
                $childExecution->getSubProcessInstance()->deleteCascade($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated, false);
301
            }
302
            $childExecution->deleteCascade($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated, false);
303
        }
304
305
        // fire activity end on active activity
306
        $activity = $this->getActivity();
307
        if ($this->isActive && $activity !== null) {
308
            // set activity instance state to cancel
309
            if ($this->activityInstanceState != ActivityInstanceState::ending()->getStateCode()) {
310
                $this->setCanceled(true);
311
                $this->performOperation(AtomicOperation::fireActivityEnd());
312
            }
313
            // set activity instance state back to 'default'
314
            // -> execution will be reused for executing more activities and we want the state to
315
            // be default initially.
316
            $this->activityInstanceState = ActivityInstanceState::default()->getStateCode();
317
        }
318
    }
319
320
    /**
321
     * Interrupts an execution
322
     */
323
    public function interrupt(string $reason, ?bool $skipCustomListeners = false, ?bool $skipIoMappings = false, ?bool $externallyTerminated = false): void
324
    {
325
        //LOG.interruptingExecution(reason, skipCustomListeners);
326
        $this->clearScope($reason, $skipCustomListeners, $skipIoMappings, $externallyTerminated);
0 ignored issues
show
Bug introduced by
It seems like $skipIoMappings can also be of type null; however, parameter $skipIoMappings of Jabe\Impl\Pvm\Runtime\Pv...utionImpl::clearScope() does only seem to accept boolean, 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

326
        $this->clearScope($reason, $skipCustomListeners, /** @scrutinizer ignore-type */ $skipIoMappings, $externallyTerminated);
Loading history...
Bug introduced by
It seems like $externallyTerminated can also be of type null; however, parameter $externallyTerminated of Jabe\Impl\Pvm\Runtime\Pv...utionImpl::clearScope() does only seem to accept boolean, 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

326
        $this->clearScope($reason, $skipCustomListeners, $skipIoMappings, /** @scrutinizer ignore-type */ $externallyTerminated);
Loading history...
Bug introduced by
It seems like $skipCustomListeners can also be of type null; however, parameter $skipCustomListeners of Jabe\Impl\Pvm\Runtime\Pv...utionImpl::clearScope() does only seem to accept boolean, 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

326
        $this->clearScope($reason, /** @scrutinizer ignore-type */ $skipCustomListeners, $skipIoMappings, $externallyTerminated);
Loading history...
327
    }
328
329
    /**
330
     * Ends an execution. Invokes end listeners for the current activity and notifies the flow scope execution
331
     * of this happening which may result in the flow scope ending.
332
     *
333
     * @param completeScope true if ending the execution contributes to completing the BPMN 2.0 scope
334
     */
335
    public function end(bool $completeScope): void
336
    {
337
        $this->setCompleteScope($completeScope);
338
339
        $this->isActive = false;
340
        $this->isEnded = true;
341
342
        if ($this->hasReplacedParent()) {
343
            $this->getParent()->replacedBy = null;
344
        }
345
346
        $this->performOperation(AtomicOperation::activityNotifyListenerEnd());
347
    }
348
349
    public function endCompensation(): void
350
    {
351
        $this->performOperation(AtomicOperation::fireActivityEnd());
352
        $this->remove();
353
354
        $parent = $this->getParent();
355
356
        if ($parent->getActivity() === null) {
357
            $parent->setActivity($this->getActivity()->getFlowScope());
0 ignored issues
show
Bug introduced by
It seems like $this->getActivity()->getFlowScope() can also be of type Jabe\Impl\Pvm\Process\ScopeImpl; however, parameter $activity of Jabe\Impl\Pvm\Runtime\Pv...tionImpl::setActivity() does only seem to accept Jabe\Impl\Pvm\PvmActivityInterface|null, 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

357
            $parent->setActivity(/** @scrutinizer ignore-type */ $this->getActivity()->getFlowScope());
Loading history...
358
        }
359
360
        $parent->signal("compensationDone", null);
361
    }
362
363
    /**
364
     * <p>Precondition: execution is already ended but this has not been propagated yet.</p>
365
     * <p>
366
     * <p>Propagates the ending of this execution to the flowscope execution; currently only supports
367
     * the process instance execution</p>
368
     */
369
    public function propagateEnd(): void
370
    {
371
        if (!$this->isEnded()) {
372
            throw new ProcessEngineException($this->__toString() . " must have ended before ending can be propagated");
373
        }
374
375
        if ($this->isProcessInstanceExecution()) {
376
            $this->performOperation(AtomicOperation::processEnd());
377
        } else {
378
            // not supported yet
379
        }
380
    }
381
382
    public function remove(): void
383
    {
384
        $parent = $this->getParent();
385
        if ($parent !== null) {
386
            $parent->removeExecution($this);
387
388
            // if the sequence counter is greater than the
389
            // sequence counter of the parent, then set
390
            // the greater sequence counter on the parent.
391
            $parentSequenceCounter = $parent->getSequenceCounter();
392
            $mySequenceCounter = $this->getSequenceCounter();
393
            if ($mySequenceCounter > $parentSequenceCounter) {
394
                $parent->setSequenceCounter($mySequenceCounter);
395
            }
396
397
            // propagate skipping configuration upwards, if it was not initially set on
398
            // the root execution
399
            $parent->skipCustomListeners |= $this->skipCustomListeners;
400
            $parent->skipIoMapping |= $this->skipIoMapping;
401
        }
402
403
        $this->isActive = false;
404
        $this->isEnded = true;
405
        $this->isRemoved = true;
406
407
        if ($this->hasReplacedParent()) {
408
            $this->getParent()->replacedBy = null;
409
        }
410
        $this->removeEventScopes();
411
    }
412
413
    abstract protected function removeExecution(PvmExecutionImpl $execution): void;
414
415
    abstract protected function addExecution(PvmExecutionImpl $execution): void;
416
417
    public function isRemoved(): bool
418
    {
419
        return $this->isRemoved;
420
    }
421
422
    public function createConcurrentExecution(): PvmExecutionImpl
423
    {
424
        if (!$this->isScope()) {
425
            throw new ProcessEngineException("Cannot create concurrent execution for " . $this);
426
        }
427
428
        // The following covers the three cases in which a concurrent execution may be created
429
        // (this execution is the root in each scenario).
430
        //
431
        // Note: this should only consider non-event-scope executions. Event-scope executions
432
        // are not relevant for the tree structure and should remain under their original parent.
433
        //
434
        //
435
        // (1) A compacted tree:
436
        //
437
        // Before:               After:
438
        //       -------               -------
439
        //       |  e1  |              |  e1 |
440
        //       -------               -------
441
        //                             /     \
442
        //                         -------  -------
443
        //                         |  e2 |  |  e3 |
444
        //                         -------  -------
445
        //
446
        // e2 replaces e1; e3 is the new root for the activity stack to instantiate
447
        //
448
        //
449
        // (2) A single child that is a scope execution
450
        // Before:               After:
451
        //       -------               -------
452
        //       |  e1 |               |  e1 |
453
        //       -------               -------
454
        //          |                  /     \
455
        //       -------           -------  -------
456
        //       |  e2 |           |  e3 |  |  e4 |
457
        //       -------           -------  -------
458
        //                            |
459
        //                         -------
460
        //                         |  e2 |
461
        //                         -------
462
        //
463
        //
464
        // e3 is created and is concurrent;
465
        // e4 is the new root for the activity stack to instantiate
466
        //
467
        // (3) Existing concurrent execution(s)
468
        // Before:               After:
469
        //       -------                    ---------
470
        //       |  e1 |                    |   e1  |
471
        //       -------                    ---------
472
        //       /     \                   /    |    \
473
        //  -------    -------      -------  -------  -------
474
        //  |  e2 | .. |  eX |      |  e2 |..|  eX |  | eX+1|
475
        //  -------    -------      -------  -------  -------
476
        //
477
        // eX+1 is concurrent and the new root for the activity stack to instantiate
478
        $children = $this->getNonEventScopeExecutions();
479
480
        // whenever we change the set of child executions we have to force an update
481
        // on the scope executions to avoid concurrent modifications (e.g. tree compaction)
482
        // that go unnoticed
483
        $this->forceUpdate();
484
485
        if (empty($children)) {
486
            // (1)
487
            $replacingExecution = $this->createExecution();
488
            $replacingExecution->setConcurrent(true);
489
            $replacingExecution->setScope(false);
490
            $replacingExecution->replace($this);
491
            $this->inactivate();
492
            $this->setActivity(null);
493
        } elseif (count($children) == 1) {
494
            // (2)
495
            $child = $children[0];
496
497
            $concurrentReplacingExecution = $this->createExecution();
498
            $concurrentReplacingExecution->setConcurrent(true);
499
            $concurrentReplacingExecution->setScope(false);
500
            $concurrentReplacingExecution->setActive(false);
501
            $concurrentReplacingExecution->onConcurrentExpand($this);
502
            $child->setParent($concurrentReplacingExecution);
503
            $this->leaveActivityInstance();
504
            $this->setActivity(null);
505
        }
506
507
        // (1), (2), and (3)
508
        $concurrentExecution = $this->createExecution();
509
        $concurrentExecution->setConcurrent(true);
510
        $concurrentExecution->setScope(false);
511
512
        return $concurrentExecution;
513
    }
514
515
    public function tryPruneLastConcurrentChild(): bool
516
    {
517
        if (count($this->getNonEventScopeExecutions()) == 1) {
518
            $lastConcurrent = $this->getNonEventScopeExecutions()[0];
519
            if ($lastConcurrent->isConcurrent()) {
520
                if (!$lastConcurrent->isScope()) {
521
                    $this->setActivity($lastConcurrent->getActivity());
522
                    $this->setTransition($lastConcurrent->getTransition());
523
                    $this->replace($lastConcurrent);
524
525
                    // Move children of lastConcurrent one level up
526
                    if ($lastConcurrent->hasChildren()) {
527
                        foreach ($lastConcurrent->getExecutionsAsCopy() as $childExecution) {
528
                            $childExecution->setParent($this);
529
                        }
530
                    }
531
532
                    // Make sure parent execution is re-activated when the last concurrent
533
                    // child execution is active
534
                    if (!$this->isActive() && $lastConcurrent->isActive()) {
535
                        $this->setActive(true);
536
                    }
537
538
                    $lastConcurrent->remove();
539
                } else {
540
                    // legacy behavior
541
                    LegacyBehavior::pruneConcurrentScope($lastConcurrent);
542
                }
543
                return true;
544
            }
545
        }
546
547
        return false;
548
    }
549
550
    public function deleteCascade(
551
        string $deleteReason,
552
        ?bool $skipCustomListeners = false,
553
        ?bool $skipIoMappings = false,
554
        ?bool $externallyTerminated = false,
555
        ?bool $skipSubprocesses = false
556
    ): void {
557
        $this->deleteReason = $deleteReason;
558
        $this->setDeleteRoot(true);
559
        $this->isEnded = true;
560
        $this->skipCustomListeners = $skipCustomListeners;
561
        $this->skipIoMapping = $skipIoMappings;
562
        $this->externallyTerminated = $externallyTerminated;
563
        $this->skipSubprocesses = $skipSubprocesses;
564
        $this->performOperation(AtomicOperation::deleteCascade());
565
    }
566
567
    public function executeEventHandlerActivity(ActivityImpl $eventHandlerActivity): void
568
    {
569
        // the target scope
570
        $flowScope = $eventHandlerActivity->getFlowScope();
571
572
        // the event scope (the current activity)
573
        $eventScope = $eventHandlerActivity->getEventScope();
574
575
        if (
576
            $eventHandlerActivity->getActivityStartBehavior() == ActivityStartBehavior::CONCURRENT_IN_FLOW_SCOPE
577
            && $flowScope != $eventScope
578
        ) {
579
            // the current scope is the event scope of the activity
580
            $this->findExecutionForScope($eventScope, $flowScope)->executeActivity($eventHandlerActivity);
0 ignored issues
show
Bug introduced by
It seems like $flowScope can also be of type null; however, parameter $targetScope of Jabe\Impl\Pvm\Runtime\Pv...findExecutionForScope() does only seem to accept Jabe\Impl\Pvm\Process\ScopeImpl, 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

580
            $this->findExecutionForScope($eventScope, /** @scrutinizer ignore-type */ $flowScope)->executeActivity($eventHandlerActivity);
Loading history...
Bug introduced by
It seems like $eventScope can also be of type null; however, parameter $currentScope of Jabe\Impl\Pvm\Runtime\Pv...findExecutionForScope() does only seem to accept Jabe\Impl\Pvm\Process\ScopeImpl, 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

580
            $this->findExecutionForScope(/** @scrutinizer ignore-type */ $eventScope, $flowScope)->executeActivity($eventHandlerActivity);
Loading history...
581
        } else {
582
            $this->executeActivity(eventHandlerActivity);
0 ignored issues
show
Bug introduced by
The constant Jabe\Impl\Pvm\Runtime\eventHandlerActivity was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
583
        }
584
    }
585
586
    // tree compaction & expansion ///////////////////////////////////////////
587
588
    /**
589
     * <p>Returns an execution that has replaced this execution for executing activities in their shared scope.</p>
590
     * <p>Invariant: this execution and getReplacedBy() execute in the same scope.</p>
591
     */
592
    abstract public function getReplacedBy(): ?PvmExecutionImpl;
593
594
    /**
595
     * Instead of {@link #getReplacedBy()}, which returns the execution that this execution was directly replaced with,
596
     * this resolves the chain of replacements (i.e. in the case the replacedBy execution itself was replaced again)
597
     */
598
    public function resolveReplacedBy(): ?PvmExecutionImpl
599
    {
600
        // follow the links of execution replacement;
601
        // note: this can be at most two hops:
602
        // case 1:
603
        //   this execution is a scope execution
604
        //     => tree may have expanded meanwhile
605
        //     => scope execution references replacing execution directly (one hop)
606
        //
607
        // case 2:
608
        //   this execution is a concurrent execution
609
        //     => tree may have compacted meanwhile
610
        //     => concurrent execution references scope execution directly (one hop)
611
        //
612
        // case 3:
613
        //   this execution is a concurrent execution
614
        //     => tree may have compacted/expanded/compacted/../expanded any number of times
615
        //     => the concurrent execution has been removed and therefore references the scope execution (first hop)
616
        //     => the scope execution may have been replaced itself again with another concurrent execution (second hop)
617
        //   note that the scope execution may have a long "history" of replacements, but only the last replacement is relevant here
618
        $replacingExecution = $this->getReplacedBy();
619
620
        if ($replacingExecution !== null) {
621
            $secondHopReplacingExecution = $replacingExecution->getReplacedBy();
622
            if ($secondHopReplacingExecution !== null) {
623
                $replacingExecution = $secondHopReplacingExecution;
624
            }
625
        }
626
627
        return $replacingExecution;
628
    }
629
630
    public function hasReplacedParent(): bool
631
    {
632
        return $this->getParent() !== null && $this->getParent()->getReplacedBy() == $this;
633
    }
634
635
    public function isReplacedByParent(): bool
636
    {
637
        return $this->getReplacedBy() !== null && $this->getReplacedBy() == $this->getParent();
638
    }
639
640
    /**
641
     * <p>Replace an execution by this execution. The replaced execution has a pointer ({@link #getReplacedBy()}) to this execution.
642
     * This pointer is maintained until the replaced execution is removed or this execution is removed/ended.</p>
643
     * <p>
644
     * <p>This is used for two cases: Execution tree expansion and execution tree compaction</p>
645
     * <ul>
646
     * <li><b>expansion</b>: Before:
647
     * <pre>
648
     *       -------
649
     *       |  e1 |  scope
650
     *       -------
651
     *     </pre>
652
     * After:
653
     * <pre>
654
     *       -------
655
     *       |  e1 |  scope
656
     *       -------
657
     *          |
658
     *       -------
659
     *       |  e2 |  cc (no scope)
660
     *       -------
661
     *     </pre>
662
     * e2 replaces e1: it should receive all entities associated with the activity currently executed
663
     * by e1; these are tasks, (local) variables, jobs (specific for the activity, not the scope)
664
     * </li>
665
     * <li><b>compaction</b>: Before:
666
     * <pre>
667
     *       -------
668
     *       |  e1 |  scope
669
     *       -------
670
     *          |
671
     *       -------
672
     *       |  e2 |  cc (no scope)
673
     *       -------
674
     *     </pre>
675
     * After:
676
     * <pre>
677
     *       -------
678
     *       |  e1 |  scope
679
     *       -------
680
     *     </pre>
681
     * e1 replaces e2: it should receive all entities associated with the activity currently executed
682
     * by e2; these are tasks, (all) variables, all jobs
683
     * </li>
684
     * </ul>
685
     *
686
     * @see #createConcurrentExecution()
687
     * @see #tryPruneLastConcurrentChild()
688
     */
689
    public function replace(PvmExecutionImpl $execution): void
690
    {
691
        // activity instance id handling
692
        $this->activityInstanceId = $execution->getActivityInstanceId();
693
        $this->isActive = $execution->isActive;
694
695
        $this->replacedBy = null;
696
        $execution->replacedBy = $this;
697
698
        $this->transitionsToTake = $execution->transitionsToTake;
699
700
        $execution->leaveActivityInstance();
701
    }
702
703
    /**
704
     * Callback on tree expansion when this execution is used as the concurrent execution
705
     * where the argument's children become a subordinate to. Note that this case is not the inverse
706
     * of replace because replace has the semantics that the replacing execution can be used to continue
707
     * execution of this execution's activity instance.
708
     */
709
    public function onConcurrentExpand(PvmExecutionImpl $scopeExecution): void
710
    {
711
        // by default, do nothing
712
    }
713
714
    // methods that translate to operations /////////////////////////////////////
715
716
    public function signal(string $signalName, $signalData): void
717
    {
718
        if ($this->getActivity() === null) {
719
            throw new PvmException("cannot signal execution " . $this->id . ": it has no current activity");
720
        }
721
722
        $activityBehavior = $this->activity->getActivityBehavior();
723
        try {
724
            $activityBehavior->signal($this, $signalName, $signalData);
725
        } catch (\Exception $e) {
726
            throw new PvmException("couldn't process signal '" . $signalName . "' on activity '" . $this->activity->getId() . "': " . $e->getMessage(), $e);
727
        }
728
    }
729
730
    public function take(): void
731
    {
732
        if ($this->transition === null) {
733
            throw new PvmException($this->__toString() . ": no transition to take specified");
734
        }
735
        $transitionImpl = $this->transition;
736
        $this->setActivity($transitionImpl->getSource());
737
        // while executing the transition, the activityInstance is 'null'
738
        // (we are not executing an activity)
739
        $this->setActivityInstanceId(null);
740
        $this->setActive(true);
741
        $this->performOperation(AtomicOperation::transitionNotifyListenerTake());
742
    }
743
744
    /**
745
     * Execute an activity which is not contained in normal flow (having no incoming sequence flows).
746
     * Cannot be called for activities contained in normal flow.
747
     * <p>
748
     * First, the ActivityStartBehavior is evaluated.
749
     * In case the start behavior is not ActivityStartBehavior#DEFAULT, the corresponding start
750
     * behavior is executed before executing the activity.
751
     * <p>
752
     * For a given activity, the execution on which this method must be called depends on the type of the start behavior:
753
     * <ul>
754
     * <li>CONCURRENT_IN_FLOW_SCOPE: scope execution for PvmActivity#getFlowScope()</li>
755
     * <li>INTERRUPT_EVENT_SCOPE: scope execution for PvmActivity#getEventScope()</li>
756
     * <li>CANCEL_EVENT_SCOPE: scope execution for PvmActivity#getEventScope()</li>
757
     * </ul>
758
     *
759
     * @param activity the activity to start
760
     */
761
    public function executeActivity(PvmActivityInterface $activity): void
762
    {
763
        if (!empty($activity->getIncomingTransitions())) {
764
            throw new ProcessEngineException("Activity is contained in normal flow and cannot be executed using executeActivity().");
765
        }
766
767
        $activityStartBehavior = $activity->getActivityStartBehavior();
768
        if (!$this->isScope() && ActivityStartBehavior::DEFAULT != $this->activityStartBehavior) {
769
            throw new ProcessEngineException("Activity '" . $activity . "' with start behavior '" . $activityStartBehavior . "'"
0 ignored issues
show
Bug introduced by
Are you sure $activity of type Jabe\Impl\Pvm\PvmActivityInterface can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

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

769
            throw new ProcessEngineException("Activity '" . /** @scrutinizer ignore-type */ $activity . "' with start behavior '" . $activityStartBehavior . "'"
Loading history...
770
            . "cannot be executed by non-scope execution.");
771
        }
772
773
        $activityImpl = $activity;
774
        $this->isEnded = false;
775
        $this->isActive = true;
776
777
        switch ($activityStartBehavior) {
778
            case ActivityStartBehavior::CONCURRENT_IN_FLOW_SCOPE:
779
                $this->nextActivity = $activityImpl;
780
                $this->performOperation(AtomicOperation::activityStartConcurrent());
781
                break;
782
783
            case ActivityStartBehavior::CANCEL_EVENT_SCOPE:
784
                $this->nextActivity = $activityImpl;
785
                $this->performOperation(AtomicOperation::activityStartCancelScope());
786
                break;
787
788
            case ActivityStartBehavior::INTERRUPT_EVENT_SCOPE:
789
                $this->nextActivity = $activityImpl;
790
                $this->performOperation(AtomicOperation::activityStartInterruptScope());
791
                break;
792
793
            default:
794
                $this->setActivity($activityImpl);
795
                $this->setActivityInstanceId(null);
796
                $this->performOperation(AtomicOperation::activityStartCreateScope());
797
                break;
798
        }
799
    }
800
801
    /**
802
     * Instantiates the given activity stack under this execution.
803
     * Sets the variables for the execution responsible to execute the most deeply nested
804
     * activity.
805
     *
806
     * @param activityStack The most deeply nested activity is the last element in the list
807
     */
808
    public function executeActivitiesConcurrent(
809
        array &$activityStack,
810
        ?PvmActivityInterface $targetActivity = null,
811
        ?PvmTransitionInterface $targetTransition = null,
812
        ?array $variables = [],
813
        ?array $localVariables = [],
814
        ?bool $skipCustomListeners = false,
815
        ?bool $skipIoMappings = false
816
    ): void {
817
        $flowScope = null;
818
        if (!empty($activityStack)) {
819
            $flowScope = $activityStack[0]->getFlowScope();
820
        } elseif ($targetActivity !== null) {
821
            $flowScope = $targetActivity->getFlowScope();
822
        } elseif ($targetTransition !== null) {
823
            $flowScope = $targetTransition->getSource()->getFlowScope();
824
        }
825
826
        $propagatingExecution = null;
827
        if ($flowScope->getActivityBehavior() instanceof ModificationObserverBehaviorInterface) {
828
            $flowScopeBehavior = $flowScope->getActivityBehavior();
829
            $propagatingExecution = $flowScopeBehavior->createInnerInstance($this);
830
        } else {
831
            $propagatingExecution = $this->createConcurrentExecution();
832
        }
833
834
        $propagatingExecution->executeActivities(
0 ignored issues
show
Bug introduced by
The method executeActivities() does not exist on Jabe\Impl\Pvm\Delegate\ActivityExecutionInterface. Did you maybe mean executeActivity()? ( Ignorable by Annotation )

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

834
        $propagatingExecution->/** @scrutinizer ignore-call */ 
835
                               executeActivities(

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...
835
            $activityStack,
836
            $targetActivity,
837
            $targetTransition,
838
            $variables,
839
            $localVariables,
840
            $skipCustomListeners,
841
            $skipIoMappings
842
        );
843
    }
844
845
    /**
846
     * Instantiates the given set of activities and returns the execution for the bottom-most activity
847
     */
848
    public function instantiateScopes(array &$activityStack, bool $skipCustomListeners, bool $skipIoMappings): array
849
    {
850
        if (empty($activityStack)) {
851
            return [];
852
        }
853
854
        $this->skipCustomListeners = $skipCustomListeners;
855
        $this->skipIoMapping = $skipIoMappings;
856
857
        $executionStartContext = new ScopeInstantiationContext();
858
859
        $instantiationStack = new InstantiationStack($activityStack);
860
        $executionStartContext->setInstantiationStack($instantiationStack);
861
        $this->setStartContext($executionStartContext);
862
863
        $this->performOperation(AtomicOperation::activityInitStackAndReturn());
864
865
        $createdExecutions = [];
866
867
        $currentExecution = $this;
868
        foreach ($activityStack as $instantiatedActivity) {
869
            // there must exactly one child execution
870
            $currentExecution = $currentExecution->getNonEventScopeExecutions()[0];
871
            if ($currentExecution->isConcurrent()) {
872
                // there may be a non-scope execution that we have to skip (e.g. multi-instance)
873
                $currentExecution = $currentExecution->getNonEventScopeExecutions()[0];
874
            }
875
876
            $createdExecutions[] = [$instantiatedActivity, $currentExecution];
877
        }
878
879
        return $createdExecutions;
880
    }
881
882
    /**
883
     * Instantiates the given activity stack. Uses this execution to execute the
884
     * highest activity in the stack.
885
     * Sets the variables for the execution responsible to execute the most deeply nested
886
     * activity.
887
     *
888
     * @param activityStack The most deeply nested activity is the last element in the list
889
     */
890
    public function executeActivities(
891
        array &$activityStack,
892
        ?PvmActivityInterface $targetActivity = null,
893
        ?PvmTransitionInterface $targetTransition = null,
894
        ?array $variables = [],
895
        ?array $localVariables = [],
896
        ?bool $skipCustomListeners = false,
897
        ?bool $skipIoMappings = false
898
    ): void {
899
        $this->skipCustomListeners = $skipCustomListeners;
900
        $this->skipIoMapping = $skipIoMappings;
901
        $this->activityInstanceId = null;
902
        $this->isEnded = false;
903
904
        if (!empty($activityStack)) {
905
            $executionStartContext = new ScopeInstantiationContext();
906
907
            $instantiationStack = new InstantiationStack($activityStack, $targetActivity, $targetTransition);
908
            $executionStartContext->setInstantiationStack($instantiationStack);
909
            $executionStartContext->setVariables($variables);
910
            $executionStartContext->setVariablesLocal($localVariables);
911
            $this->setStartContext($executionStartContext);
912
913
            $this->performOperation(AtomicOperation::activityInitStack());
914
        } elseif ($targetActivity !== null) {
915
            $this->setVariables($variables);
916
            $this->setVariablesLocal($localVariables);
917
            $this->setActivity($targetActivity);
918
            $this->performOperation(AtomicOperation::activityStartCReateScope());
919
        } elseif ($targetTransition !== null) {
920
            $this->setVariables($variables);
921
            $this->setVariablesLocal($localVariables);
922
            $this->setActivity($targetTransition->getSource());
923
            $this->setTransition($targetTransition);
924
            $this->performOperation(AtomicOperation::trasitionStartNotifyListenerTake());
0 ignored issues
show
Bug introduced by
The method trasitionStartNotifyListenerTake() does not exist on Jabe\Impl\Pvm\Runtime\AtomicOperation. Did you maybe mean transitionStartNotifyListenerTake()? ( Ignorable by Annotation )

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

924
            $this->performOperation(AtomicOperation::/** @scrutinizer ignore-call */ trasitionStartNotifyListenerTake());

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...
925
        }
926
    }
927
928
    public function findInactiveConcurrentExecutions(PvmActivityInterface $activity): array
929
    {
930
        $inactiveConcurrentExecutionsInActivity = [];
931
        if ($this->isConcurrent()) {
932
            return $this->getParent()->findInactiveChildExecutions($activity);
933
        } elseif (!$this->isActive()) {
934
            $inactiveConcurrentExecutionsInActivity[] = $this;
935
        }
936
        return $inactiveConcurrentExecutionsInActivity;
937
    }
938
939
    public function findInactiveChildExecutions(PvmActivityInterface $activity): array
940
    {
941
        $inactiveConcurrentExecutionsInActivity = [];
942
        $concurrentExecutions = $this->getAllChildExecutions();
943
        foreach ($concurrentExecutions as $concurrentExecution) {
944
            if ($concurrentExecution->getActivity() == $activity && !$concurrentExecution->isActive()) {
945
                $inactiveConcurrentExecutionsInActivity[] = $concurrentExecution;
946
            }
947
        }
948
949
        return $inactiveConcurrentExecutionsInActivity;
950
    }
951
952
    protected function getAllChildExecutions(): array
953
    {
954
        $childExecutions = [];
955
        foreach ($this->getExecutions() as $childExecution) {
956
            $childExecutions[] = $childExecution;
957
            $childExecutions = array_merge($childExecutions, $childExecution->getAllChildExecutions());
958
        }
959
        return $childExecutions;
960
    }
961
962
    public function leaveActivityViaTransition($outgoingTransition, ?array $_recyclableExecutions = []): void
963
    {
964
        if ($outgoingTransition instanceof PvmTransitionInterface) {
965
            $_transitions = [$outgoingTransition];
966
        } else {
967
            $_transitions = $outgoingTransition;
968
        }
969
        $recyclableExecutions = [];
970
        if (!empty($_recyclableExecutions)) {
971
            $recyclableExecutions = $_recyclableExecutions;
972
        }
973
974
        // if recyclable executions size is greater
975
        // than 1, then the executions are joined and
976
        // the activity is left with 'this' execution,
977
        // if it is not not the last concurrent execution.
978
        // therefore it is necessary to remove the local
979
        // variables (event if it is the last concurrent
980
        // execution).
981
        if (count($recyclableExecutions) > 1) {
982
            $this->removeVariablesLocalInternal();
983
        }
984
985
        // mark all recyclable executions as ended
986
        // if the list of recyclable executions also
987
        // contains 'this' execution, then 'this' execution
988
        // is also marked as ended. (if 'this' execution is
989
        // pruned, then the local variables are not copied
990
        // to the parent execution)
991
        // this is a workaround to not delete all recyclable
992
        // executions and create a new execution which leaves
993
        // the activity.
994
        foreach ($recyclableExecutions as $execution) {
995
            $execution->setEnded(true);
996
        }
997
998
        // remove 'this' from recyclable executions to
999
        // leave the activity with 'this' execution
1000
        // (when 'this' execution is the last concurrent
1001
        // execution, then 'this' execution will be pruned,
1002
        // and the activity is left with the scope
1003
        // execution)
1004
        foreach ($recyclableExecutions as $key => $execution) {
1005
            if ($execution == $this) {
1006
                unset($recyclableExecutions[$key]);
1007
            }
1008
        }
1009
1010
        foreach ($recyclableExecutions as $execution) {
1011
            $execution->end(empty($_transitions));
1012
        }
1013
1014
        $propagatingExecution = this;
0 ignored issues
show
Bug introduced by
The constant Jabe\Impl\Pvm\Runtime\this was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1015
        if ($this->getReplacedBy() !== null) {
1016
            $propagatingExecution = $this->getReplacedBy();
1017
        }
1018
1019
        $propagatingExecution->isActive = true;
1020
        $propagatingExecution->isEnded = false;
1021
1022
        if (empty($_transitions)) {
1023
            $propagatingExecution->end(!$propagatingExecution->isConcurrent());
1024
        } else {
1025
            $propagatingExecution->setTransitionsToTake($_transitions);
1026
            $propagatingExecution->performOperation(AtomicOperation::transitionNotifyListenerEnd());
1027
        }
1028
    }
1029
1030
    abstract protected function removeVariablesLocalInternal(): void;
1031
1032
    public function isActive(?string $activityId = null): bool
1033
    {
1034
        if ($activityId !== null) {
1035
            return $this->findExecution($activityId) !== null;
1036
        }
1037
        $this->isActive;
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
1038
    }
1039
1040
    public function inactivate(): void
1041
    {
1042
        $this->isActive = false;
1043
    }
1044
1045
    // executions ///////////////////////////////////////////////////////////////
1046
1047
    abstract public function getExecutions(): array;
1048
1049
    abstract public function getExecutionsAsCopy(): array;
1050
1051
    public function getNonEventScopeExecutions(): array
1052
    {
1053
        $children = $this->getExecutions();
1054
        $result = [];
1055
1056
        foreach ($children as $child) {
1057
            if (!$child->isEventScope()) {
1058
                $result[] = $child;
1059
            }
1060
        }
1061
1062
        return $result;
1063
    }
1064
1065
    public function getEventScopeExecutions(): array
1066
    {
1067
        $children = $this->getExecutions();
1068
        $result = [];
1069
1070
        foreach ($children as $child) {
1071
            if ($child->isEventScope()) {
1072
                $result[] = $child;
1073
            }
1074
        }
1075
1076
        return $result;
1077
    }
1078
1079
    public function findExecution(string $activityId): ?PvmExecutionImpl
1080
    {
1081
        if (
1082
            ($this->getActivity() !== null)
1083
            && ($this->getActivity()->getId() == $activityId)
1084
        ) {
1085
            return $this;
1086
        }
1087
        foreach ($this->getExecutions() as $nestedExecution) {
1088
            $result = $nestedExecution->findExecution($activityId);
1089
            if ($result !== null) {
1090
                return result;
0 ignored issues
show
Bug introduced by
The constant Jabe\Impl\Pvm\Runtime\result was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1091
            }
1092
        }
1093
        return null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return null returns the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\PvmProcess...erface::findExecution() of Jabe\Impl\Pvm\PvmExecutionInterface.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
1094
    }
1095
1096
    public function findExecutions(string $activityId): array
1097
    {
1098
        $matchingExecutions = [];
1099
        $this->collectExecutions($activityId, $matchingExecutions);
1100
1101
        return $matchingExecutions;
1102
    }
1103
1104
    protected function collectExecutions(string $activityId, array &$executions): array
1105
    {
1106
        if (
1107
            ($this->getActivity() !== null)
1108
            && ($this->getActivity()->getId() == $activityId)
1109
        ) {
1110
            $executions[] = $this;
1111
        }
1112
1113
        foreach ($this->getExecutions() as $nestedExecution) {
1114
            $nestedExecution->collectExecutions($activityId, $executions);
1115
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
1116
    }
1117
1118
    public function findActiveActivityIds(): array
1119
    {
1120
        $activeActivityIds = [];
1121
        $this->collectActiveActivityIds($activeActivityIds);
1122
        return $activeActivityIds;
1123
    }
1124
1125
    protected function collectActiveActivityIds(array &$activeActivityIds): void
1126
    {
1127
        $activity = $this->getActivity();
1128
        if ($this->isActive && $activity !== null) {
1129
            $activeActivityIds[] = $activity->getId();
1130
        }
1131
1132
        foreach ($this->getExecutions() as $execution) {
1133
            $execution->collectActiveActivityIds($activeActivityIds);
1134
        }
1135
    }
1136
1137
    // business key /////////////////////////////////////////
1138
1139
    public function getProcessBusinessKey(): ?string
1140
    {
1141
        return $this->getProcessInstance()->getBusinessKey();
1142
    }
1143
1144
    public function setProcessBusinessKey(string $businessKey): void
1145
    {
1146
        $processInstance = $this->getProcessInstance();
1147
        $processInstance->setBusinessKey($businessKey);
1148
1149
        $historyLevel = Context::getCommandContext()->getProcessEngineConfiguration()->getHistoryLevel();
0 ignored issues
show
Bug introduced by
The method getHistoryLevel() does not exist on Jabe\Impl\Cfg\ProcessEngineConfigurationImpl. Did you maybe mean getHistoryLevelCommand()? ( Ignorable by Annotation )

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

1149
        $historyLevel = Context::getCommandContext()->getProcessEngineConfiguration()->/** @scrutinizer ignore-call */ getHistoryLevel();

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...
1150
        if ($historyLevel->isHistoryEventProduced(HistoryEventTypes::processInstanceUpdate(), $processInstance)) {
1151
            HistoryEventProcessor::processHistoryEvents(new class ($processInstance) extends HistoryEventCreator {
1152
                private $processInstance;
1153
1154
                public function __construct($processInstance)
1155
                {
1156
                    $this->processInstance = $processInstance;
1157
                }
1158
1159
                public function createHistoryEvent(HistoryEventProducerInterface $producer): HistoryEvent
1160
                {
1161
                    return $producer->createProcessInstanceUpdateEvt($this->processInstance);
1162
                }
1163
            });
1164
        }
1165
    }
1166
1167
    public function getBusinessKey(): ?string
1168
    {
1169
        if ($this->isProcessInstanceExecution()) {
1170
            return $this->businessKey;
1171
        }
1172
        return $this->getProcessBusinessKey();
1173
    }
1174
1175
    // process definition ///////////////////////////////////////////////////////
1176
1177
    public function setProcessDefinition(ProcessDefinitionImpl $processDefinition): void
1178
    {
1179
        $this->processDefinition = $processDefinition;
1180
    }
1181
1182
    public function getProcessDefinition(): ProcessDefinitionImpl
1183
    {
1184
        return $this->processDefinition;
1185
    }
1186
1187
    // process instance /////////////////////////////////////////////////////////
1188
1189
    /**
1190
     * ensures initialization and returns the process instance.
1191
     */
1192
1193
    abstract public function getProcessInstance(): PvmExecutionImpl;
1194
1195
    abstract public function setProcessInstance(PvmExecutionImpl $pvmExecutionImpl): void;
1196
1197
    // case instance id /////////////////////////////////////////////////////////
1198
1199
    /*public function getCaseInstanceId(): ?string
1200
    {
1201
        return $this->caseInstanceId;
1202
    }
1203
1204
    public function setCaseInstanceId(string $caseInstanceId): void
1205
    {
1206
        $this->caseInstanceId = $caseInstanceId;
1207
    }*/
1208
1209
    // activity /////////////////////////////////////////////////////////////////
1210
1211
    /**
1212
     * ensures initialization and returns the activity
1213
     */
1214
    public function getActivity(): ?ActivityImpl
1215
    {
1216
        return $this->activity;
1217
    }
1218
1219
    public function getActivityId(): ?string
1220
    {
1221
        $activity = $this->getActivity();
1222
        if ($activity !== null) {
1223
            return $activity->getId();
1224
        }
1225
        return null;
1226
    }
1227
1228
    public function getCurrentActivityName(): ?string
1229
    {
1230
        $activity = $this->getActivity();
1231
        if ($activity !== null) {
1232
            return $activity->getName();
1233
        }
1234
        return null;
1235
    }
1236
1237
    public function getCurrentActivityId(): ?string
1238
    {
1239
        return $this->getActivityId();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getActivityId() also could return the type null which is incompatible with the return type mandated by Jabe\Delegate\DelegateEx...:getCurrentActivityId() of string.
Loading history...
1240
    }
1241
1242
    public function setActivity(?PvmActivityInterface $activity = null): void
1243
    {
1244
        $this->activity = $activity;
1245
    }
1246
1247
    public function enterActivityInstance(): void
1248
    {
1249
        $activity = $this->getActivity();
1250
        $activityInstanceId = $this->generateActivityInstanceId($activity->getId());
0 ignored issues
show
Unused Code introduced by
The assignment to $activityInstanceId is dead and can be removed.
Loading history...
1251
1252
        //LOG.debugEnterActivityInstance(this, getParentActivityInstanceId());
1253
1254
        // <LEGACY>: in general, io mappings may only exist when the activity is scope
1255
        // however, for multi instance activities, the inner activity does not become a scope
1256
        // due to the presence of an io mapping. In that case, it is ok to execute the io mapping
1257
        // anyway because the multi-instance body already ensures variable isolation
1258
        $this->executeIoMapping();
1259
1260
        if ($activity->isScope()) {
1261
            $this->initializeTimerDeclarations();
1262
        }
1263
1264
        $this->activityInstanceEndListenersFailed = false;
1265
    }
1266
1267
    public function activityInstanceStarting(): void
1268
    {
1269
        $this->activityInstanceState = ActivityInstanceState::starting()->getStateCode();
1270
    }
1271
1272
    public function activityInstanceStarted(): void
1273
    {
1274
        $this->activityInstanceState = ActivityInstanceState::default()->getStateCode();
1275
    }
1276
1277
    public function activityInstanceDone(): void
1278
    {
1279
        $this->activityInstanceState = ActivityInstanceState::ending()->getStateCode();
1280
    }
1281
1282
    public function activityInstanceEndListenerFailure(): void
1283
    {
1284
        $this->activityInstanceEndListenersFailed = true;
1285
    }
1286
1287
    abstract protected function generateActivityInstanceId(string $activityId): string;
1288
1289
    public function leaveActivityInstance(): void
1290
    {
1291
        if ($this->activityInstanceId !== null) {
1292
            //LOG.debugLeavesActivityInstance(this, activityInstanceId);
1293
        }
1294
        $this->activityInstanceId = $this->getParentActivityInstanceId();
1295
1296
        $this->activityInstanceState = ActivityInstanceState::default()->getStateCode();
1297
        $this->activityInstanceEndListenersFailed = false;
1298
    }
1299
1300
    public function getParentActivityInstanceId(): ?string
1301
    {
1302
        if ($this->isProcessInstanceExecution()) {
1303
            return $this->getId();
1304
        } else {
1305
            return $this->getParent()->getActivityInstanceId();
1306
        }
1307
    }
1308
1309
    public function setActivityInstanceId(?string $activityInstanceId): void
1310
    {
1311
        $this->activityInstanceId = $activityInstanceId;
1312
    }
1313
1314
    // parent ///////////////////////////////////////////////////////////////////
1315
1316
    /**
1317
     * ensures initialization and returns the parent
1318
     */
1319
    abstract public function getParent(): ?PvmExecutionImpl;
1320
1321
    public function getParentId(): ?string
1322
    {
1323
        $parent = $this->getParent();
1324
        if ($parent !== null) {
1325
            return $parent->getId();
1326
        }
1327
        return null;
1328
    }
1329
1330
    public function hasChildren(): bool
1331
    {
1332
        return !empty($this->getExecutions());
1333
    }
1334
1335
    /**
1336
     * Sets the execution's parent and updates the old and new parents' set of
1337
     * child executions
1338
     */
1339
    public function setParent(PvmExecutionImpl $parent): void
1340
    {
1341
        $currentParent = $this->getParent();
1342
1343
        $this->setParentExecution($parent);
1344
1345
        if ($currentParent !== null) {
1346
            $currentParent->removeExecution($this);
1347
        }
1348
1349
        if ($parent !== null) {
1350
            $parent->addExecution($this);
1351
        }
1352
    }
1353
1354
    /**
1355
     * Use #setParent to also update the child execution sets
1356
     */
1357
    abstract public function setParentExecution(PvmExecutionImpl $parent): void;
1358
1359
    // super- and subprocess executions /////////////////////////////////////////
1360
1361
    abstract public function getSuperExecution(): ?PvmExecutionImpl;
1362
1363
    abstract public function setSuperExecution(PvmExecutionImpl $superExecution): void;
1364
1365
    abstract public function getSubProcessInstance(): ?PvmExecutionImpl;
1366
1367
    abstract public function setSubProcessInstance(?PvmExecutionImpl $subProcessInstance): void;
1368
1369
    // super case execution /////////////////////////////////////////////////////
1370
1371
    //abstract public function CmmnExecution getSuperCaseExecution();
1372
1373
    //abstract public function void setSuperCaseExecution(CmmnExecution superCaseExecution);
1374
1375
    // sub case execution ///////////////////////////////////////////////////////
1376
1377
    //abstract public function CmmnExecution getSubCaseInstance();
1378
1379
    //abstract public function void setSubCaseInstance(CmmnExecution subCaseInstance);
1380
1381
    // scopes ///////////////////////////////////////////////////////////////////
1382
1383
    protected function getScopeActivity(): ScopeImpl
1384
    {
1385
        $scope = null;
1386
        // this if condition is important during process instance startup
1387
        // where the activity of the process instance execution may not be aligned
1388
        // with the execution tree
1389
        if ($this->isProcessInstanceExecution()) {
1390
            $scope = $this->getProcessDefinition();
1391
        } else {
1392
            $scope = $this->getActivity();
1393
        }
1394
        return $scope;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $scope could return the type null which is incompatible with the type-hinted return Jabe\Impl\Pvm\Process\ScopeImpl. Consider adding an additional type-check to rule them out.
Loading history...
1395
    }
1396
1397
    public function isScope(): bool
1398
    {
1399
        return $this->isScope;
1400
    }
1401
1402
    public function setScope(bool $isScope): void
1403
    {
1404
        $this->isScope = $isScope;
1405
    }
1406
1407
    /**
1408
     * For a given target flow scope, this method returns the corresponding scope execution.
1409
     * <p>
1410
     * Precondition: the execution is active and executing an activity.
1411
     * Can be invoked for scope and non scope executions.
1412
     *
1413
     * @param targetFlowScope mixed - scope activity or process definition for which the scope execution should be found
1414
     * @return PvmExecutionImpl the scope execution for the provided targetFlowScope
1415
     */
1416
    public function findExecutionForFlowScope($targetFlowScope): ?PvmExecutionImpl
1417
    {
1418
        if (is_string($targetFlowScope)) {
1419
            $targetScopeId = $targetFlowScope;
1420
            EnsureUtil::ensureNotNull("target scope id", "targetScopeId", "targetScopeId", $targetScopeId);
1421
1422
            $currentActivity = $this->getActivity();
1423
            EnsureUtil::ensureNotNull("activity of current execution", "currentActivity", $currentActivity);
1424
1425
            $walker = new FlowScopeWalker($currentActivity);
1426
            $targetFlowScope = $walker->walkUntil(new class ($targetScopeId) implements WalkConditionInterface {
1427
1428
                private $targetScopeId;
1429
1430
                public function __construct(string $targetScopeId)
1431
                {
1432
                    $this->targetScopeId = $targetScopeId;
1433
                }
1434
1435
                public function isFulfilled($scope = null): bool
1436
                {
1437
                    return $scope === null || $scope->getId() == $this->targetScopeId;
1438
                }
1439
            });
1440
1441
            if ($targetFlowScope === null) {
1442
                //throw LOG.scopeNotFoundException(targetScopeId, this->getId());
1443
            }
1444
1445
            return $this->findExecutionForFlowScope($targetFlowScope);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findExecut...Scope($targetFlowScope) also could return the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\Delegate\A...ExecutionForFlowScope() of Jabe\Impl\Pvm\Delegate\ActivityExecution.
Loading history...
1446
        } elseif ($targetFlowScope instanceof PvmScopeInterface) {
1447
            // if this execution is not a scope execution, use the parent
1448
            $scopeExecution = $this->isScope() ? $this : $this->getParent();
1449
1450
            $currentActivity = $this->getActivity();
1451
            EnsureUtil::ensureNotNull("activity of current execution", "currentActivity", $currentActivity);
1452
1453
            // if this is a scope execution currently executing a non scope activity
1454
            $currentActivity = $currentActivity->isScope() ? $currentActivity : $currentActivity->getFlowScope();
1455
1456
            return $scopeExecution->findExecutionForScope($currentActivity, $targetFlowScope);
0 ignored issues
show
Bug introduced by
It seems like $currentActivity can also be of type null; however, parameter $currentScope of Jabe\Impl\Pvm\Runtime\Pv...findExecutionForScope() does only seem to accept Jabe\Impl\Pvm\Process\ScopeImpl, 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

1456
            return $scopeExecution->findExecutionForScope(/** @scrutinizer ignore-type */ $currentActivity, $targetFlowScope);
Loading history...
Bug Best Practice introduced by
The expression return $scopeExecution->...vity, $targetFlowScope) also could return the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\Delegate\A...ExecutionForFlowScope() of Jabe\Impl\Pvm\Delegate\ActivityExecution.
Loading history...
Bug introduced by
The method findExecutionForScope() does not exist on null. ( Ignorable by Annotation )

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

1456
            return $scopeExecution->/** @scrutinizer ignore-call */ findExecutionForScope($currentActivity, $targetFlowScope);

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...
1457
        }
0 ignored issues
show
Bug Best Practice introduced by
The expression ImplicitReturnNode returns the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\Delegate\A...ExecutionForFlowScope() of Jabe\Impl\Pvm\Delegate\ActivityExecution.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
1458
    }
1459
1460
    public function findExecutionForScope(ScopeImpl $currentScope, ScopeImpl $targetScope): ?PvmExecutionImpl
1461
    {
1462
        if (!$targetScope->isScope()) {
1463
            throw new ProcessEngineException("Target scope must be a scope.");
1464
        }
1465
1466
        $activityExecutionMapping = $this->createActivityExecutionMapping($currentScope);
1467
        $scopeExecution = null;
1468
        foreach ($activityExecutionMapping as $map) {
1469
            if ($map[0] == $targetScope) {
1470
                $scopeExecution = $map[1];
1471
            }
1472
        }
1473
        if ($scopeExecution === null) {
1474
            // the target scope is scope but no corresponding execution was found
1475
            // => legacy behavior
1476
            $scopeExecution = LegacyBehavior::getScopeExecution(targetScope, activityExecutionMapping);
0 ignored issues
show
Bug introduced by
The constant Jabe\Impl\Pvm\Runtime\activityExecutionMapping was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Jabe\Impl\Pvm\Runtime\targetScope was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1477
        }
1478
        return $scopeExecution;
1479
    }
1480
1481
    protected function getFlowScopeExecution(): PvmExecutionImpl
1482
    {
1483
        if (!$this->isScope || CompensationBehavior::executesNonScopeCompensationHandler($this)) {
1484
            // LEGACY: a correct implementation should also skip a compensation-throwing parent scope execution
1485
            // (since compensation throwing activities are scopes), but this cannot be done for backwards compatibility
1486
            // where a compensation throwing activity was no scope (and we would wrongly skip an execution in that case)
1487
            return $this->getParent()->getFlowScopeExecution();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getParent(...getFlowScopeExecution() could return the type null which is incompatible with the type-hinted return Jabe\Impl\Pvm\Runtime\PvmExecutionImpl. Consider adding an additional type-check to rule them out.
Loading history...
1488
        }
1489
        return $this;
1490
    }
1491
1492
    protected function getFlowScope(): ScopeImpl
1493
    {
1494
        $activity = $this->getActivity();
1495
1496
        if (
1497
            !$activity->isScope() || $this->activityInstanceId === null
1498
            || ($activity->isScope() && !$this->isScope() && $activity->getActivityBehavior() instanceof CompositeActivityBehaviorInterface)
1499
        ) {
1500
            // if
1501
            // - this is a scope execution currently executing a non scope activity
1502
            // - or it is not scope but the current activity is (e.g. can happen during activity end, when the actual
1503
            //   scope execution has been removed and the concurrent parent has been set to the scope activity)
1504
            // - or it is asyncBefore/asyncAfter
1505
1506
            return $activity->getFlowScope();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $activity->getFlowScope() could return the type null which is incompatible with the type-hinted return Jabe\Impl\Pvm\Process\ScopeImpl. Consider adding an additional type-check to rule them out.
Loading history...
1507
        }
1508
        return $activity;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $activity could return the type null which is incompatible with the type-hinted return Jabe\Impl\Pvm\Process\ScopeImpl. Consider adding an additional type-check to rule them out.
Loading history...
1509
    }
1510
1511
    /**
1512
     * Creates an extended mapping based on this execution and the given existing mapping.
1513
     * Any entry <code>mapping</code> in mapping that corresponds to an ancestor scope of
1514
     * <code>currentScope</code> is reused.
1515
     */
1516
    protected function createActivityExecutionMapping(
1517
        ?ScopeImpl $currentScope = null,
1518
        ?array $mapping = null
1519
    ): array {
1520
        if ($currentScope !== null && $mapping !== null) {
1521
            if (!$this->isScope()) {
1522
                throw new ProcessEngineException("Execution must be a scope execution");
1523
            }
1524
            if (!$currentScope->isScope()) {
1525
                throw new ProcessEngineException("Current scope must be a scope.");
1526
            }
1527
1528
            // collect all ancestor scope executions unless one is encountered that is already in "mapping"
1529
            $scopeExecutionCollector = new ScopeExecutionCollector();
1530
            (new ExecutionWalker($this))
1531
                ->addPreVisitor($scopeExecutionCollector)
1532
                ->walkWhile(new class ($mapping) implements WalkConditionInterface {
1533
                    private $mapping;
1534
1535
                    public function __construct(array $mapping)
1536
                    {
1537
                        $this->mapping = $mapping;
1538
                    }
1539
1540
                    public function isFulfilled($element = null): bool
1541
                    {
1542
                        $contains = false;
1543
                        foreach ($this->mapping as $map) {
1544
                            if ($map[1] == $element) {
1545
                                $contains = true;
1546
                                break;
1547
                            }
1548
                        }
1549
                        return $element === null || $contains;
1550
                    }
1551
                });
1552
            $scopeExecutions = $scopeExecutionCollector->getScopeExecutions();
1553
1554
            // collect all ancestor scopes unless one is encountered that is already in "mapping"
1555
            $scopeCollector = new ScopeCollector();
1556
            (new FlowScopeWalker($currentScope))
1557
                ->addPreVisitor($scopeCollector)
1558
                ->walkWhile(new class ($mapping) implements WalkConditionInterface {
1559
                    private $mapping;
1560
1561
                    public function __construct(array $mapping)
1562
                    {
1563
                        $this->mapping = $mapping;
1564
                    }
1565
1566
                    public function isFulfilled($element = null): bool
1567
                    {
1568
                        $contains = false;
1569
                        foreach ($this->mapping as $map) {
1570
                            if ($map[0] == $element) {
1571
                                $contains = true;
1572
                                break;
1573
                            }
1574
                        }
1575
                        return $element === null || $contains;
1576
                    }
1577
                });
1578
1579
            $scopes = $scopeCollector->getScopes();
1580
            $outerScope = new \stdClass();
1581
            $outerScope->scopes = $scopes;
1582
            $outerScope->scopeExecutions = $scopeExecutions;
1583
1584
            // add all ancestor scopes and scopeExecutions that are already in "mapping"
1585
            // and correspond to ancestors of the topmost previously collected scope
1586
            $topMostScope = $scopes[count($scopes) - 1];
1587
            (new FlowScopeWalker($topMostScope->getFlowScope()))
1588
                ->addPreVisitor(new class ($outerScope, $mapping) implements TreeVisitorInterface {
1589
                    private $outerScope;
1590
                    private $mapping;
1591
1592
                    public function __construct($outerScope, $mapping)
1593
                    {
1594
                        $this->outerScope = $outerScope;
1595
                        $this->mapping = $mapping;
1596
                    }
1597
1598
                    public function visit($obj): void
1599
                    {
1600
                        $this->outerScope->scopes[] = $obj;
1601
1602
                        $priorMappingExecution = null;
1603
                        foreach ($this->mapping as $map) {
1604
                            if ($map[0] == $obj) {
1605
                                $priorMappingExecution = $map[1];
1606
                            }
1607
                        }
1608
1609
                        $contains = false;
1610
                        foreach ($this->outerScope->scopeExecutions as $exec) {
1611
                            if ($exec == $priorMappingExecution) {
1612
                                $contains = true;
1613
                                break;
1614
                            }
1615
                        }
1616
1617
                        if ($priorMappingExecution !== null && !$contains) {
1618
                            $this->outerScope->scopeExecutions[] = $priorMappingExecution;
1619
                        }
1620
                    }
1621
                })
1622
                ->walkWhile();
1623
1624
            $scopes = $outerScope->scopes;
1625
            $scopeExecutions = $outerScope->scopeExecutions;
1626
1627
            if (count($scopes) == count($scopeExecutions)) {
1628
                // the trees are in sync
1629
                $result = [];
1630
                for ($i = 0; $i < count($scopes); $i += 1) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1631
                    $result[] = [$scopes[$i], $scopeExecutions[$i]];
1632
                }
1633
                return $result;
1634
            } else {
1635
                // Wounderful! The trees are out of sync. This is due to legacy behavior
1636
                return LegacyBehavior::createActivityExecutionMapping($scopeExecutions, $scopes);
1637
            }
1638
        } elseif ($currentScope !== null) {
1639
            if (!$this->isScope()) {
1640
                throw new ProcessEngineException("Execution must be a scope execution");
1641
            }
1642
            if (!$currentScope->isScope()) {
1643
                throw new ProcessEngineException("Current scope must be a scope.");
1644
            }
1645
1646
            // A single path in the execution tree from a leaf (no child executions) to the root
1647
            // may in fact contain multiple executions that correspond to leaves in the activity instance hierarchy.
1648
            //
1649
            // This is because compensation throwing executions have child executions. In that case, the
1650
            // flow scope hierarchy is not aligned with the scope execution hierarchy: There is a scope
1651
            // execution for a compensation-throwing event that is an ancestor of this execution,
1652
            // while these events are not ancestor scopes of currentScope.
1653
            //
1654
            // The strategy to deal with this situation is as follows:
1655
            // 1. Determine all executions that correspond to leaf activity instances
1656
            // 2. Order the leaf executions in top-to-bottom fashion
1657
            // 3. Iteratively build the activity execution mapping based on the leaves in top-to-bottom order
1658
            //    3.1. For the first leaf, create the activity execution mapping regularly
1659
            //    3.2. For every following leaf, rebuild the mapping but reuse any scopes and scope executions
1660
            //         that are part of the mapping created in the previous iteration
1661
            //
1662
            // This process ensures that the resulting mapping does not contain scopes that are not ancestors
1663
            // of currentScope and that it does not contain scope executions for such scopes.
1664
            // For any execution hierarchy that does not involve compensation, the number of iterations in step 3
1665
            // should be 1, i.e. there are no other leaf activity instance executions in the hierarchy.
1666
1667
            // 1. Find leaf activity instance executions
1668
            $leafCollector = new LeafActivityInstanceExecutionCollector();
1669
            (new ExecutionWalker($this))->addPreVisitor($leafCollector)->walkUntil();
1670
1671
            $leafCollector->removeLeaf($this);
1672
            $leaves = $leafCollector->getLeaves();
1673
1674
            // 2. Order them from top to bottom
1675
            $leaves = array_reverse($leaves);
1676
1677
            // 3. Iteratively extend the mapping for every additional leaf
1678
            $mapping = [];
1679
            foreach ($leaves as $leaf) {
1680
                $leafFlowScope = $leaf->getFlowScope();
1681
                $leafFlowScopeExecution = $leaf->getFlowScopeExecution();
1682
1683
                $mapping = $leafFlowScopeExecution->createActivityExecutionMapping($leafFlowScope, $mapping);
1684
            }
1685
1686
            // finally extend the mapping for the current execution
1687
            // (note that the current execution need not be a leaf itself)
1688
            $mapping = $this->createActivityExecutionMapping($currentScope, $mapping);
1689
1690
            return $mapping;
1691
        } elseif ($currentScope === null && $mapping === null) {
1692
            $currentActivity = $this->getActivity();
1693
            EnsureUtil::ensureNotNull("activity of current execution", "currentActivity", $currentActivity);
1694
1695
            $flowScope = $this->getFlowScope();
1696
            $flowScopeExecution = $this->getFlowScopeExecution();
1697
1698
            return $flowScopeExecution->createActivityExecutionMapping($flowScope);
1699
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
1700
    }
1701
1702
    // toString /////////////////////////////////////////////////////////////////
1703
1704
    public function __toString()
1705
    {
1706
        if ($this->isProcessInstanceExecution()) {
1707
            return "ProcessInstance[" . $this->getToStringIdentity() . "]";
1708
        } else {
1709
            return ($this->isConcurrent ? "Concurrent" : "") . ($this->isScope ? "Scope" : "") . "Execution[" . $this->getToStringIdentity() . "]";
1710
        }
1711
    }
1712
1713
    protected function getToStringIdentity(): string
1714
    {
1715
        return $this->id;
1716
    }
1717
1718
    // variables ////////////////////////////////////////////
1719
1720
    public function getVariableScopeKey(): string
1721
    {
1722
        return "execution";
1723
    }
1724
1725
    public function getParentVariableScope(): ?AbstractVariableScope
1726
    {
1727
        return $this->getParent();
1728
    }
1729
1730
    /**
1731
     * {@inheritDoc}
1732
     */
1733
    public function setVariable(string $variableName, $value, ?string $targetActivityId = null): void
1734
    {
1735
        $activityId = $this->getActivityId();
1736
        if (!empty($activityId) && $activityId == $targetActivityId) {
1737
            $this->setVariableLocal($variableName, $value);
1738
        } else {
1739
            $executionForFlowScope = $this->findExecutionForFlowScope($targetActivityId);
1740
            if ($executionForFlowScope !== null) {
1741
                $executionForFlowScope->setVariableLocal($variableName, $value);
1742
            }
1743
        }
1744
    }
1745
1746
    // sequence counter ///////////////////////////////////////////////////////////
1747
1748
    public function getSequenceCounter(): int
1749
    {
1750
        return $this->sequenceCounter;
1751
    }
1752
1753
    public function setSequenceCounter(int $sequenceCounter): void
1754
    {
1755
        $this->sequenceCounter = $sequenceCounter;
1756
    }
1757
1758
    public function incrementSequenceCounter(): void
1759
    {
1760
        $this->sequenceCounter += 1;
1761
    }
1762
1763
    // Getter / Setters ///////////////////////////////////
1764
1765
    public function isExternallyTerminated(): bool
1766
    {
1767
        return $this->externallyTerminated;
1768
    }
1769
1770
    public function setExternallyTerminated(bool $externallyTerminated): void
1771
    {
1772
        $this->externallyTerminated = $externallyTerminated;
1773
    }
1774
1775
    public function getDeleteReason(): ?string
1776
    {
1777
        return $this->deleteReason;
1778
    }
1779
1780
    public function setDeleteReason(string $deleteReason): void
1781
    {
1782
        $this->deleteReason = $deleteReason;
1783
    }
1784
1785
    public function isDeleteRoot(): bool
1786
    {
1787
        return $this->deleteRoot;
1788
    }
1789
1790
    public function setDeleteRoot(bool $deleteRoot): void
1791
    {
1792
        $this->deleteRoot = $deleteRoot;
1793
    }
1794
1795
    public function getTransition(): ?TransitionImpl
1796
    {
1797
        return $this->transition;
1798
    }
1799
1800
    public function getTransitionsToTake(): array
1801
    {
1802
        return $this->transitionsToTake;
1803
    }
1804
1805
    public function setTransitionsToTake(array $transitionsToTake): void
1806
    {
1807
        $this->transitionsToTake = $transitionsToTake;
1808
    }
1809
1810
    public function getCurrentTransitionId(): ?string
1811
    {
1812
        $transition = $this->getTransition();
1813
        if ($transition !== null) {
1814
            return $transition->getId();
1815
        }
1816
        return null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return null returns the type null which is incompatible with the return type mandated by Jabe\Delegate\DelegateEx...etCurrentTransitionId() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
1817
    }
1818
1819
    public function setTransition(PvmTransitionInterface $transition): void
1820
    {
1821
        $this->transition = $transition;
1822
    }
1823
1824
    public function isConcurrent(): bool
1825
    {
1826
        return $this->isConcurrent;
1827
    }
1828
1829
    public function setConcurrent(bool $isConcurrent): void
1830
    {
1831
        $this->isConcurrent = $isConcurrent;
0 ignored issues
show
Bug Best Practice introduced by
The expression ImplicitReturnNode returns the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\Delegate\A...erface::setConcurrent() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
1832
    }
1833
1834
    public function setActive(bool $isActive): void
1835
    {
1836
        $this->isActive = $isActive;
1837
    }
1838
1839
    public function setEnded(bool $isEnded): void
1840
    {
1841
        $this->isEnded = $isEnded;
1842
    }
1843
1844
    public function isEnded(): bool
1845
    {
1846
        return $this->isEnded;
1847
    }
1848
1849
    public function isCanceled(): bool
1850
    {
1851
        return ActivityInstanceState::canceled()->getStateCode() == $this->activityInstanceState;
1852
    }
1853
1854
    public function setCanceled(bool $canceled): void
1855
    {
1856
        if ($this->canceled) {
1857
            $this->activityInstanceState = ActivityInstanceState::canceled()->getStateCode();
1858
        }
1859
    }
1860
1861
    public function isCompleteScope(): bool
1862
    {
1863
        return ActivityInstanceState::scopeComplete()->getStateCode() == $this->activityInstanceState;
1864
    }
1865
1866
    public function setCompleteScope(bool $completeScope): void
1867
    {
1868
        if ($completeScope && !$this->isCanceled()) {
1869
            $this->activityInstanceState = ActivityInstanceState::scopeComplete()->getStateCode();
1870
        }
1871
    }
1872
1873
    public function setPreserveScope(bool $preserveScope): void
1874
    {
1875
        $this->preserveScope = $preserveScope;
1876
    }
1877
1878
    public function isPreserveScope(): bool
1879
    {
1880
        return $this->preserveScope;
1881
    }
1882
1883
    public function getActivityInstanceState(): int
1884
    {
1885
        return $this->activityInstanceState;
1886
    }
1887
1888
    public function isInState(ActivityInstanceState $state): bool
1889
    {
1890
        return $this->activityInstanceState == $state->getStateCode();
0 ignored issues
show
introduced by
The method getStateCode() does not exist on Jabe\Impl\Pvm\Runtime\ActivityInstanceState. Maybe you want to declare this class abstract? ( Ignorable by Annotation )

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

1890
        return $this->activityInstanceState == $state->/** @scrutinizer ignore-call */ getStateCode();
Loading history...
1891
    }
1892
1893
    public function hasFailedOnEndListeners(): bool
1894
    {
1895
        return $this->activityInstanceEndListenersFailed;
1896
    }
1897
1898
    public function isEventScope(): bool
1899
    {
1900
        return $this->isEventScope;
1901
    }
1902
1903
    public function setEventScope(bool $isEventScope): void
1904
    {
1905
        $this->isEventScope = $isEventScope;
1906
    }
1907
1908
    public function getScopeInstantiationContext(): ?ScopeInstantiationContext
1909
    {
1910
        return $this->scopeInstantiationContext;
1911
    }
1912
1913
    public function disposeScopeInstantiationContext(): void
1914
    {
1915
        $this->scopeInstantiationContext = null;
1916
1917
        $parent = $this;
1918
        while (($parent = $parent->getParent()) !== null && $parent->scopeInstantiationContext !== null) {
1919
            $parent->scopeInstantiationContext = null;
1920
        }
1921
    }
1922
1923
    public function getNextActivity(): PvmActivityInterface
1924
    {
1925
        return $this->nextActivity;
1926
    }
1927
1928
    public function isProcessInstanceExecution(): bool
1929
    {
1930
        return $this->getParent() === null;
1931
    }
1932
1933
    public function setStartContext(ScopeInstantiationContext $startContext): void
1934
    {
1935
        $this->scopeInstantiationContext = $startContext;
1936
    }
1937
1938
    public function setIgnoreAsync(bool $ignoreAsync): void
1939
    {
1940
        $this->ignoreAsync = $ignoreAsync;
1941
    }
1942
1943
    public function isIgnoreAsync(): bool
1944
    {
1945
        return $this->ignoreAsync;
1946
    }
1947
1948
    public function setStarting(bool $isStarting): void
1949
    {
1950
        $this->isStarting = $isStarting;
1951
    }
1952
1953
    public function isStarting(): bool
1954
    {
1955
        return $this->isStarting;
1956
    }
1957
1958
    public function isProcessInstanceStarting(): bool
1959
    {
1960
        return $this->getProcessInstance()->isStarting();
1961
    }
1962
1963
    public function setProcessInstanceStarting(bool $starting): void
1964
    {
1965
        $this->getProcessInstance()->setStarting($starting);
1966
    }
1967
1968
    public function setNextActivity(PvmActivityInterface $nextActivity): void
1969
    {
1970
        $this->nextActivity = $nextActivity;
1971
    }
1972
1973
    public function getParentScopeExecution(bool $considerSuperExecution): PvmExecutionImpl
1974
    {
1975
        if ($this->isProcessInstanceExecution()) {
1976
            if ($considerSuperExecution && $this->getSuperExecution() !== null) {
1977
                $superExecution = $this->getSuperExecution();
1978
                if ($superExecution->isScope()) {
1979
                    return $superExecution;
1980
                } else {
1981
                    return $superExecution->getParent();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $superExecution->getParent() could return the type null which is incompatible with the type-hinted return Jabe\Impl\Pvm\Runtime\PvmExecutionImpl. Consider adding an additional type-check to rule them out.
Loading history...
1982
                }
1983
            } else {
1984
                return null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return null returns the type null which is incompatible with the type-hinted return Jabe\Impl\Pvm\Runtime\PvmExecutionImpl.
Loading history...
1985
            }
1986
        } else {
1987
            $parent = $this->getParent();
1988
            if ($parent->isScope()) {
1989
                return $parent;
1990
            } else {
1991
                return $parent->getParent();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $parent->getParent() could return the type null which is incompatible with the type-hinted return Jabe\Impl\Pvm\Runtime\PvmExecutionImpl. Consider adding an additional type-check to rule them out.
Loading history...
1992
            }
1993
        }
1994
    }
1995
1996
    /**
1997
     * Contains the delayed variable events, which will be dispatched on a save point.
1998
     */
1999
    protected $delayedEvents = [];
2000
2001
    /**
2002
     * Delays and stores the given DelayedVariableEvent on the process instance.
2003
     *
2004
     * @param delayedVariableEvent the DelayedVariableEvent which should be store on the process instance
2005
     */
2006
    public function delayEvent($target, ?VariableEvent $variableEvent = null): void
2007
    {
2008
        if ($target instanceof PvmExecutionImpl) {
2009
            $delayedVariableEvent = new DelayedVariableEvent($target, $variableEvent);
0 ignored issues
show
Bug introduced by
It seems like $variableEvent can also be of type null; however, parameter $event of Jabe\Impl\Persistence\En...bleEvent::__construct() does only seem to accept Jabe\Impl\Core\Variable\Event\VariableEvent, 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

2009
            $delayedVariableEvent = new DelayedVariableEvent($target, /** @scrutinizer ignore-type */ $variableEvent);
Loading history...
2010
            $this->delayEvent($delayedVariableEvent);
2011
        } elseif ($target instanceof DelayedVariableEvent) {
2012
            //if process definition has no conditional events the variable events does not have to be delayed
2013
            $hasConditionalEvents = $this->getProcessDefinition()->getProperties()->get(BpmnProperties::hasConditionalEvents());
2014
            if ($hasConditionalEvents === null || $hasConditionalEvents != true) {
2015
                return;
2016
            }
2017
2018
            if ($this->isProcessInstanceExecution()) {
2019
                $this->delayedEvents[] = $target;
2020
            } else {
2021
                $this->getProcessInstance()->delayEvent($target);
2022
            }
2023
        }
2024
    }
2025
2026
    /**
2027
     * The current delayed variable events.
2028
     *
2029
     * @return a list of DelayedVariableEvent objects
2030
     */
2031
    public function getDelayedEvents(): array
2032
    {
2033
        if ($this->isProcessInstanceExecution()) {
2034
            return $this->delayedEvents;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->delayedEvents returns the type array which is incompatible with the documented return type Jabe\Impl\Pvm\Runtime\a.
Loading history...
2035
        }
2036
        return $this->getProcessInstance()->getDelayedEvents();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getProcess...e()->getDelayedEvents() returns the type array which is incompatible with the documented return type Jabe\Impl\Pvm\Runtime\a.
Loading history...
2037
    }
2038
2039
    /**
2040
     * Cleares the current delayed variable events.
2041
     */
2042
    public function clearDelayedEvents(): void
2043
    {
2044
        if ($this->isProcessInstanceExecution()) {
2045
            $this->delayedEvents = [];
2046
        } else {
2047
            $this->getProcessInstance()->clearDelayedEvents();
2048
        }
2049
    }
2050
2051
    /**
2052
     * Dispatches the current delayed variable events and performs the given atomic operation
2053
     * if the current state was not changed.
2054
     *
2055
     * @param continuation the atomic operation continuation which should be executed
2056
     */
2057
    public function dispatchDelayedEventsAndPerformOperation($continuation = null): void
2058
    {
2059
        if ($continuation instanceof PvmAtomicOperationInterface) {
2060
            $this->dispatchDelayedEventsAndPerformOperation(new class ($continuation) implements CallbackInterface {
2061
                private $atomicOperation;
2062
2063
                public function __construct(PvmAtomicOperationInterface $atomicOperation)
2064
                {
2065
                    $this->atomicOperation = $atomicOperation;
2066
                }
2067
2068
                public function callback($param)
2069
                {
2070
                    $param->performOperation($this->atomicOperation);
2071
                    return null;
2072
                }
2073
            });
2074
        } elseif ($continuation instanceof CallbackInterface) {
2075
            $execution = $this;
2076
2077
            if (empty($execution->getDelayedEvents())) {
2078
                $this->continueExecutionIfNotCanceled($continuation, $execution);
2079
                return;
2080
            }
2081
2082
            $this->continueIfExecutionDoesNotAffectNextOperation(new class ($this) implements CallbackInterface {
2083
                private $scope;
2084
2085
                public function __construct(PvmExecutionImpl $scope)
2086
                {
2087
                    $this->scope = $scope;
2088
                }
2089
2090
                public function callback(PvmExecutionImpl $execution)
2091
                {
2092
                    $this->scope->dispatchScopeEvents($execution);
2093
                    return null;
2094
                }
2095
            }, new class () implements CallbackInterface {
0 ignored issues
show
Bug introduced by
The call to anonymous//src/Impl/Pvm/...pl.php$7::__construct() has too few arguments starting with scope. ( Ignorable by Annotation )

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

2095
            }, /** @scrutinizer ignore-call */ new class () implements CallbackInterface {

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
2096
                private $scope;
2097
                private $continuation;
2098
2099
                public function __construct(PvmExecutionImpl $scope, $continuation)
2100
                {
2101
                    $this->scope = $scope;
2102
                    $this->continuation = $continuation;
2103
                }
2104
2105
                public function callback(PvmExecutionImpl $execution)
2106
                {
2107
                    $this->scope->continueExecutionIfNotCanceled($this->continuation, $execution);
2108
                    return null;
2109
                }
2110
            }, $execution);
2111
        }
2112
    }
2113
2114
    /**
2115
     * Executes the given depending operations with the given execution.
2116
     * The execution state will be checked with the help of the activity instance id and activity id of the execution before and after
2117
     * the dispatching callback call. If the id's are not changed the
2118
     * continuation callback is called.
2119
     *
2120
     * @param dispatching         the callback to dispatch the variable events
2121
     * @param continuation        the callback to continue with the next atomic operation
2122
     * @param execution           the execution which is used for the execution
2123
     */
2124
    public function continueIfExecutionDoesNotAffectNextOperation(
2125
        CallbackInterface $dispatching,
2126
        CallbackInterface $continuation,
2127
        PvmExecutionImpl $execution
2128
    ): void {
2129
        $lastActivityId = $execution->getActivityId();
2130
        $lastActivityInstanceId = $this->getActivityInstanceId($execution);
2131
2132
        $dispatching->callback($execution);
2133
2134
        $execution = $execution->getReplacedBy() !== null ? $execution->getReplacedBy() : $execution;
2135
        $currentActivityInstanceId = $this->getActivityInstanceId($execution);
2136
        $currentActivityId = $execution->getActivityId();
2137
2138
        //if execution was canceled or was changed during the dispatch we should not execute the next operation
2139
        //since another atomic operation was executed during the dispatching
2140
        if (!$execution->isCanceled() && $this->isOnSameActivity($lastActivityInstanceId, $lastActivityId, $currentActivityInstanceId, $currentActivityId)) {
2141
            $continuation->callback($execution);
2142
        }
2143
    }
2144
2145
    protected function continueExecutionIfNotCanceled(?CallbackInterface $continuation, PvmExecutionImpl $execution): void
2146
    {
2147
        if ($continuation !== null && !$execution->isCanceled()) {
2148
            $continuation->callback($execution);
2149
        }
2150
    }
2151
2152
    /**
2153
     * Dispatches the current delayed variable events on the scope of the given execution.
2154
     *
2155
     * @param execution the execution on which scope the delayed variable should be dispatched
2156
     */
2157
    protected function dispatchScopeEvents(PvmExecutionImpl $execution): void
2158
    {
2159
        $scopeExecution = $execution->isScope() ? $execution : $execution->getParent();
2160
2161
        $delayedEvents = $scopeExecution->getDelayedEvents();
2162
        $scopeExecution->clearDelayedEvents();
2163
2164
        $activityInstanceIds = [];
2165
        $activityIds = [];
2166
        $this->initActivityIds($delayedEvents, $activityInstanceIds, $activityIds);
2167
2168
        //For each delayed variable event we have to check if the delayed event can be dispatched,
2169
        //the check will be done with the help of the activity id and activity instance id.
2170
        //That means it will be checked if the dispatching changed the execution tree in a way that we can't dispatch the
2171
        //the other delayed variable events. We have to check the target scope with the last activity id and activity instance id
2172
        //and also the replace pointer if it exist. Because on concurrency the replace pointer will be set on which we have
2173
        //to check the latest state.
2174
        foreach ($delayedEvents as $event) {
2175
            $targetScope = $event->getTargetScope();
2176
            $replaced = $targetScope->getReplacedBy() !== null ? $targetScope->getReplacedBy() : $targetScope;
2177
            $this->dispatchOnSameActivity($targetScope, $replaced, $activityIds, $activityInstanceIds, $event);
2178
        }
2179
    }
2180
2181
    /**
2182
     * Initializes the given maps with the target scopes and current activity id's and activity instance id's.
2183
     *
2184
     * @param delayedEvents       the delayed events which contains the information about the target scope
2185
     * @param activityInstanceIds the map which maps target scope to activity instance id
2186
     * @param activityIds         the map which maps target scope to activity id
2187
     */
2188
    protected function initActivityIds(
2189
        array $delayedEvents,
2190
        array &$activityInstanceIds,
2191
        array &$activityIds
2192
    ): void {
2193
        foreach ($delayedEvents as $event) {
2194
            $targetScope = $event->getTargetScope();
2195
2196
            $targetScopeActivityInstanceId = $this->getActivityInstanceId($targetScope);
2197
            $activityInstanceIds[] = [$targetScope, $targetScopeActivityInstanceId];
2198
            $activityIds[] = [$targetScope, $targetScope->getActivityId()];
2199
        }
2200
    }
2201
2202
    /**
2203
     * Dispatches the delayed variable event, if the target scope and replaced by scope (if target scope was replaced) have the
2204
     * same activity Id's and activity instance id's.
2205
     *
2206
     * @param targetScope          the target scope on which the event should be dispatched
2207
     * @param replacedBy           the replaced by pointer which should have the same state
2208
     * @param activityIds          the map which maps scope to activity id
2209
     * @param activityInstanceIds  the map which maps scope to activity instance id
2210
     * @param delayedVariableEvent the delayed variable event which should be dispatched
2211
     */
2212
    private function dispatchOnSameActivity(
2213
        PvmExecutionImpl $targetScope,
2214
        PvmExecutionImpl $replacedBy,
2215
        array $activityIds,
2216
        array $activityInstanceIds,
2217
        DelayedVariableEvent $delayedVariableEvent
2218
    ): void {
2219
        //check if the target scope has the same activity id and activity instance id
2220
        //since the dispatching was started
2221
        $currentActivityInstanceId = $this->getActivityInstanceId($targetScope);
2222
        $currentActivityId = $targetScope->getActivityId();
2223
2224
        $lastActivityInstanceId = null;
2225
        foreach ($activityInstanceIds as $map) {
2226
            if ($map[0] == $targetScope) {
2227
                $lastActivityInstanceId = $map[1];
2228
            }
2229
        }
2230
2231
        $lastActivityId = null;
2232
        foreach ($activityIds as $map) {
2233
            if ($map[0] == $targetScope) {
2234
                $lastActivityId = $map[1];
2235
            }
2236
        }
2237
2238
        $onSameAct = $this->isOnSameActivity($lastActivityInstanceId, $lastActivityId, $currentActivityInstanceId, $currentActivityId);
2239
2240
        //If not we have to check the replace pointer,
2241
        //which was set if a concurrent execution was created during the dispatching.
2242
        if ($targetScope != $replacedBy && !$onSameAct) {
2243
            $currentActivityInstanceId = $this->getActivityInstanceId($replacedBy);
2244
            $currentActivityId = $replacedBy->getActivityId();
2245
            $onSameAct = $this->isOnSameActivity($lastActivityInstanceId, $lastActivityId, $currentActivityInstanceId, $currentActivityId);
2246
        }
2247
2248
        //dispatching
2249
        if ($onSameAct && $this->isOnDispatchableState($targetScope)) {
2250
            $targetScope->dispatchEvent($delayedVariableEvent->getEvent());
2251
        }
2252
    }
2253
2254
    /**
2255
     * Checks if the given execution is on a dispatchable state.
2256
     * That means if the current activity is not a leaf in the activity tree OR
2257
     * it is a leaf but not a scope OR it is a leaf, a scope
2258
     * and the execution is in state DEFAULT, which means not in state
2259
     * Starting, Execute or Ending. For this states it is
2260
     * prohibited to trigger conditional events, otherwise unexpected behavior can appear.
2261
     *
2262
     * @return bool - true if the execution is on a dispatchable state, false otherwise
2263
     */
2264
    private function isOnDispatchableState(PvmExecutionImpl $targetScope): bool
2265
    {
2266
        $targetActivity = $targetScope->getActivity();
2267
        return
2268
            //if not leaf, activity id is null -> dispatchable
2269
            $targetScope->getActivityId() === null ||
2270
            // if leaf and not scope -> dispatchable
2271
            !$targetActivity->isScope() ||
2272
            // if leaf, scope and state in default -> dispatchable
2273
            ($targetScope->isInState(ActivityInstanceState::default()));
2274
    }
2275
2276
    /**
2277
     * Compares the given activity instance id's and activity id's to check if the execution is on the same
2278
     * activity as before an operation was executed. The activity instance id's can be null on transitions.
2279
     * In this case the activity Id's have to be equal, otherwise the execution changed.
2280
     *
2281
     * @param string|null - lastActivityInstanceId    the last activity instance id
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
2282
     * @param string|null - lastActivityId            the last activity id
2283
     * @param string|null - currentActivityInstanceId the current activity instance id
2284
     * @param string|null - currentActivityId         the current activity id
2285
     * @return bool - true if the execution is on the same activity, otherwise false
2286
     */
2287
    private function isOnSameActivity(
2288
        ?string $lastActivityInstanceId,
2289
        ?string $lastActivityId,
2290
        ?string $currentActivityInstanceId,
2291
        ?string $currentActivityId
2292
    ): bool {
2293
        return
2294
            //activityInstanceId's can be null on transitions, so the activityId must be equal
2295
            (($lastActivityInstanceId === null && $lastActivityInstanceId == $currentActivityInstanceId && $lastActivityId == $currentActivityId)
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $currentActivityInstanceId of type null|string against $lastActivityInstanceId of type null|string; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
2296
            //if activityInstanceId's are not null they must be equal -> otherwise execution changed
2297
            || ($lastActivityInstanceId !== null && $lastActivityInstanceId == $currentActivityInstanceId
2298
            && ($lastActivityId === null || $lastActivityId == $currentActivityId)));
2299
    }
2300
2301
    /**
2302
     * Returns the activity instance id for the given execution.
2303
     *
2304
     * @param PvmExecutionImpl|null - targetScope the execution for which the activity instance id should be returned
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
2305
     * @return string the activity instance id
2306
     */
2307
    private function getActivityInstanceId(?PvmExecutionImpl $targetScope = null): ?string
2308
    {
2309
        if ($targetScope !== null) {
2310
            if ($targetScope->isConcurrent()) {
2311
                return $targetScope->getActivityInstanceId();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $targetScope->getActivityInstanceId() also could return the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\Delegate\A...getActivityInstanceId() of string.
Loading history...
2312
            } else {
2313
                $targetActivity = $targetScope->getActivity();
2314
                if (($targetActivity !== null && empty($targetActivity->getActivities()))) {
2315
                    return $targetScope->getActivityInstanceId();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $targetScope->getActivityInstanceId() also could return the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\Delegate\A...getActivityInstanceId() of string.
Loading history...
2316
                }
2317
                return $targetScope->getParentActivityInstanceId();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $targetScope->get...entActivityInstanceId() also could return the type null which is incompatible with the return type mandated by Jabe\Impl\Pvm\Delegate\A...getActivityInstanceId() of string.
Loading history...
2318
            }
2319
        }
2320
        return $this->activityInstanceId;
2321
    }
2322
2323
    /**
2324
     * Returns the newest incident in this execution
2325
     *
2326
     * @param incidentType the type of new incident
2327
     * @param configuration configuration of the incident
2328
     * @return new incident
2329
     */
2330
    public function createIncident(string $incidentType, string $configuration, ?string $message = null): IncidentInterface
2331
    {
2332
        $incidentContext = $this->createIncidentContext($configuration);
2333
2334
        return IncidentHandling::createIncident($incidentType, $incidentContext, $message);
0 ignored issues
show
Bug introduced by
It seems like $message can also be of type null; however, parameter $message of Jabe\Impl\Incident\Incid...dling::createIncident() 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

2334
        return IncidentHandling::createIncident($incidentType, $incidentContext, /** @scrutinizer ignore-type */ $message);
Loading history...
Bug Best Practice introduced by
The expression return Jabe\Impl\Inciden...identContext, $message) returns the type Jabe\Runtime\IncidentInterface which is incompatible with the documented return type Jabe\Impl\Pvm\Runtime\new.
Loading history...
2335
    }
2336
2337
    protected function createIncidentContext(string $configuration): IncidentContext
2338
    {
2339
        $incidentContext = new IncidentContext();
0 ignored issues
show
Bug introduced by
The call to Jabe\Impl\Incident\IncidentContext::__construct() has too few arguments starting with incident. ( Ignorable by Annotation )

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

2339
        $incidentContext = /** @scrutinizer ignore-call */ new IncidentContext();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
2340
2341
        $incidentContext->setTenantId($this->getTenantId());
2342
        $incidentContext->setProcessDefinitionId($this->getProcessDefinitionId());
2343
        $incidentContext->setExecutionId($this->getId());
2344
        $incidentContext->setActivityId($this->getActivityId());
0 ignored issues
show
Bug introduced by
It seems like $this->getActivityId() can also be of type null; however, parameter $activityId of Jabe\Impl\Incident\Incid...ontext::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

2344
        $incidentContext->setActivityId(/** @scrutinizer ignore-type */ $this->getActivityId());
Loading history...
2345
        $incidentContext->setConfiguration($configuration);
2346
2347
        return $incidentContext;
2348
    }
2349
2350
    /**
2351
     * Resolves an incident with given id.
2352
     *
2353
     * @param incidentId
2354
     */
2355
    public function resolveIncident(string $incidentId): void
2356
    {
2357
        $incident = Context::getCommandContext()
2358
            ->getIncidentManager()
2359
            ->findIncidentById($incidentId);
2360
2361
        $incidentContext = new IncidentContext($incident);
2362
        IncidentHandling::removeIncidents($incident->getIncidentType(), $incidentContext, true);
2363
    }
2364
2365
    public function findIncidentHandler(string $incidentType): ?IncidentHandlerInterface
2366
    {
2367
        $incidentHandlers = Context::getProcessEngineConfiguration()->getIncidentHandlers();
0 ignored issues
show
Bug introduced by
The method getIncidentHandlers() 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

2367
        $incidentHandlers = Context::getProcessEngineConfiguration()->/** @scrutinizer ignore-call */ getIncidentHandlers();

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...
2368
        if (array_key_exists($incidentType, $incidentHandlers)) {
2369
            return $incidentHandlers[$incidentType];
2370
        }
2371
        return null;
2372
    }
2373
}
2374