Total Complexity | 80 |
Total Lines | 455 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like GetActivityInstanceCmd often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use GetActivityInstanceCmd, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
34 | class GetActivityInstanceCmd implements CommandInterface |
||
35 | { |
||
36 | protected $processInstanceId; |
||
37 | |||
38 | public function __construct(string $processInstanceId) |
||
39 | { |
||
40 | $this->processInstanceId = $processInstanceId; |
||
41 | } |
||
42 | |||
43 | public function execute(CommandContext $commandContext) |
||
44 | { |
||
45 | EnsureUtil::ensureNotNull("processInstanceId", "processInstanceId", $this->processInstanceId); |
||
46 | $executionList = $this->loadProcessInstance($this->processInstanceId, $commandContext); |
||
47 | |||
48 | if (empty($executionList)) { |
||
49 | return null; |
||
50 | } |
||
51 | |||
52 | $this->checkGetActivityInstance($this->processInstanceId, $commandContext); |
||
53 | |||
54 | $nonEventScopeExecutions = $this->filterNonEventScopeExecutions($executionList); |
||
55 | $leaves = $this->filterLeaves($nonEventScopeExecutions); |
||
56 | // Leaves must be ordered in a predictable way (e.g. by ID) |
||
57 | // in order to return a stable execution tree with every repeated invocation of this command. |
||
58 | // For legacy process instances, there may miss scope executions for activities that are now a scope. |
||
59 | // In this situation, there may be multiple scope candidates for the same instance id; which one |
||
60 | // can depend on the order the leaves are iterated. |
||
61 | $this->orderById($leaves); |
||
62 | |||
63 | $processInstance = $this->filterProcessInstance($executionList); |
||
64 | |||
65 | if ($processInstance->isEnded()) { |
||
66 | return null; |
||
67 | } |
||
68 | |||
69 | $incidents = $this->groupIncidentIdsByExecutionId($commandContext); |
||
70 | |||
71 | // create act instance for process instance |
||
72 | $processActInst = $this->createActivityInstance( |
||
73 | $processInstance, |
||
74 | $processInstance->getProcessDefinition(), |
||
75 | $this->processInstanceId, |
||
76 | null, |
||
|
|||
77 | $incidents |
||
78 | ); |
||
79 | |||
80 | $activityInstances = []; |
||
81 | $activityInstances[$this->processInstanceId] = $processActInst; |
||
82 | |||
83 | $transitionInstances = []; |
||
84 | |||
85 | foreach ($leaves as $leaf) { |
||
86 | // skip leafs without activity, e.g. if only the process instance exists after cancellation |
||
87 | // it will not have an activity set |
||
88 | if ($leaf->getActivity() === null) { |
||
89 | continue; |
||
90 | } |
||
91 | |||
92 | $activityExecutionMapping = $leaf->createActivityExecutionMapping(); |
||
93 | $scopeInstancesToCreate = $activityExecutionMapping; |
||
94 | |||
95 | // create an activity/transition instance for each leaf that executes a non-scope activity |
||
96 | // and does not throw compensation |
||
97 | if ($leaf->getActivityInstanceId() !== null) { |
||
98 | if (!CompensationBehavior::isCompensationThrowing($leaf) || LegacyBehavior::isCompensationThrowing($leaf, $activityExecutionMapping)) { |
||
99 | $parentActivityInstanceId = null; |
||
100 | foreach ($activityExecutionMapping as $pair) { |
||
101 | if ($pair[0] == $leaf->getActivity()->getFlowScope()) { |
||
102 | $parentActivityInstanceId = $pair[1]->getParentActivityInstanceId(); |
||
103 | break; |
||
104 | } |
||
105 | } |
||
106 | |||
107 | $leafInstance = $this->createActivityInstance( |
||
108 | $leaf, |
||
109 | $leaf->getActivity(), |
||
110 | $leaf->getActivityInstanceId(), |
||
111 | $parentActivityInstanceId, |
||
112 | $incidents |
||
113 | ); |
||
114 | $activityInstances[$leafInstance->getId()] = $leafInstance; |
||
115 | |||
116 | $actToRemove = $leaf->getActivity(); |
||
117 | foreach ($scopeInstancesToCreate as $key => $pair) { |
||
118 | if ($pair[0] == $actToRemove) { |
||
119 | unset($scopeInstancesToCreate[$key]); |
||
120 | break; |
||
121 | } |
||
122 | } |
||
123 | } |
||
124 | } else { |
||
125 | $transitionInstance = $this->createTransitionInstance($leaf, $incidents); |
||
126 | $transitionInstances[$transitionInstance->getId()] = $transitionInstance; |
||
127 | $actToRemove = $leaf->getActivity(); |
||
128 | foreach ($scopeInstancesToCreate as $key => $pair) { |
||
129 | if ($pair[0] == $actToRemove) { |
||
130 | unset($scopeInstancesToCreate[$key]); |
||
131 | break; |
||
132 | } |
||
133 | } |
||
134 | } |
||
135 | |||
136 | LegacyBehavior::removeLegacyNonScopesFromMapping($scopeInstancesToCreate); |
||
137 | $actToRemove = $leaf->getProcessDefinition(); |
||
138 | foreach ($scopeInstancesToCreate as $key => $pair) { |
||
139 | if ($pair[0] == $actToRemove) { |
||
140 | unset($scopeInstancesToCreate[$key]); |
||
141 | break; |
||
142 | } |
||
143 | } |
||
144 | |||
145 | // create an activity instance for each scope (including compensation throwing executions) |
||
146 | foreach ($scopeInstancesToCreate as $pair) { |
||
147 | $scope = $pair[0]; |
||
148 | $scopeExecution = $pair[1]; |
||
149 | |||
150 | $activityInstanceId = null; |
||
151 | $parentActivityInstanceId = null; |
||
152 | |||
153 | $activityInstanceId = $scopeExecution->getParentActivityInstanceId(); |
||
154 | |||
155 | foreach ($activityExecutionMapping as $pair2) { |
||
156 | if ($pair2[0] == $scope->getFlowScope()) { |
||
157 | $parentActivityInstanceId = $pair2[1]->getParentActivityInstanceId(); |
||
158 | break; |
||
159 | } |
||
160 | } |
||
161 | |||
162 | if (array_key_exists($activityInstanceId, $activityInstances)) { |
||
163 | continue; |
||
164 | } else { |
||
165 | // regardless of the tree structure (compacted or not), the scope's activity instance id |
||
166 | // is the activity instance id of the parent execution and the parent activity instance id |
||
167 | // of that is the actual parent activity instance id |
||
168 | $scopeInstance = $this->createActivityInstance( |
||
169 | $scopeExecution, |
||
170 | $scope, |
||
171 | $activityInstanceId, |
||
172 | $parentActivityInstanceId, |
||
173 | $incidents |
||
174 | ); |
||
175 | $activityInstances[$activityInstanceId] = $scopeInstance; |
||
176 | } |
||
177 | } |
||
178 | } |
||
179 | |||
180 | LegacyBehavior::repairParentRelationships(array_values($activityInstances), $this->processInstanceId); |
||
181 | $this->populateChildInstances($activityInstances, $transitionInstances); |
||
182 | |||
183 | return $processActInst; |
||
184 | } |
||
185 | |||
186 | protected function checkGetActivityInstance(string $processInstanceId, CommandContext $commandContext): void |
||
187 | { |
||
188 | foreach ($commandContext->getProcessEngineConfiguration()->getCommandCheckers() as $checker) { |
||
189 | $checker->checkReadProcessInstance($processInstanceId); |
||
190 | } |
||
191 | } |
||
192 | |||
193 | protected function orderById(array &$leaves): void |
||
194 | { |
||
195 | usort($leaves, function (ExecutionEntity $o1, ExecutionEntity $o2) { |
||
196 | return ($o1->getId() < $o2->getId()) ? -1 : 1; |
||
197 | }); |
||
198 | } |
||
199 | |||
200 | protected function createActivityInstance( |
||
201 | PvmExecutionImpl $scopeExecution, |
||
202 | ScopeImpl $scope, |
||
203 | string $activityInstanceId, |
||
204 | string $parentActivityInstanceId, |
||
205 | array $incidentsByExecution |
||
206 | ): ActivityInstanceImpl { |
||
207 | $actInst = new ActivityInstanceImpl(); |
||
208 | |||
209 | $actInst->setId($activityInstanceId); |
||
210 | $actInst->setParentActivityInstanceId($parentActivityInstanceId); |
||
211 | $actInst->setProcessInstanceId($scopeExecution->getProcessInstanceId()); |
||
212 | $actInst->setProcessDefinitionId($scopeExecution->getProcessDefinitionId()); |
||
213 | $actInst->setBusinessKey($scopeExecution->getBusinessKey()); |
||
214 | $actInst->setActivityId($scope->getId()); |
||
215 | |||
216 | $name = $scope->getName(); |
||
217 | if ($name === null) { |
||
218 | $name = $scope->getProperty("name"); |
||
219 | } |
||
220 | $actInst->setActivityName($name); |
||
221 | |||
222 | if ($scope->getId() == $scopeExecution->getProcessDefinition()->getId()) { |
||
223 | $actInst->setActivityType("processDefinition"); |
||
224 | } else { |
||
225 | $actInst->setActivityType($scope->getProperty("type")); |
||
226 | } |
||
227 | |||
228 | $executionIds = []; |
||
229 | $incidentIds = []; |
||
230 | $incidents = []; |
||
231 | |||
232 | $executionIds[] = $scopeExecution->getId(); |
||
233 | |||
234 | $executionActivity = $scopeExecution->getActivity(); |
||
235 | |||
236 | // do not collect incidents if scopeExecution is a compacted subtree |
||
237 | // and we currently create the scope activity instance |
||
238 | if ($executionActivity === null || $executionActivity == $scope) { |
||
239 | $incidentIds = array_merge($incidentIds, $this->getIncidentIds($incidentsByExecution, $scopeExecution)); |
||
240 | $incidents = array_merge($incidents, $this->getIncidents($incidentsByExecution, $scopeExecution)); |
||
241 | } |
||
242 | |||
243 | foreach ($scopeExecution->getNonEventScopeExecutions() as $childExecution) { |
||
244 | // add all concurrent children that are not in an activity |
||
245 | if ($childExecution->isConcurrent() && $childExecution->getActivityId() === null) { |
||
246 | $executionIds[] = $childExecution->getId(); |
||
247 | $incidentIds = array_merge($incidentIds, $this->getIncidentIds($incidentsByExecution, $childExecution)); |
||
248 | $incidents = array_merge($incidents, $this->getIncidents($incidentsByExecution, $childExecution)); |
||
249 | } |
||
250 | } |
||
251 | |||
252 | $actInst->setExecutionIds($executionIds); |
||
253 | $actInst->setIncidentIds($incidentIds); |
||
254 | $actInst->setIncidents($incidents); |
||
255 | |||
256 | return $actInst; |
||
257 | } |
||
258 | |||
259 | protected function createTransitionInstance( |
||
260 | PvmExecutionImpl $execution, |
||
261 | array $incidentsByExecution |
||
262 | ): TransitionInstanceImpl { |
||
263 | $transitionInstance = new TransitionInstanceImpl(); |
||
264 | |||
265 | // can use execution id as persistent ID for transition as an execution |
||
266 | // can execute as most one transition at a time. |
||
267 | $transitionInstance->setId($execution->getId()); |
||
268 | $transitionInstance->setParentActivityInstanceId($execution->getParentActivityInstanceId()); |
||
269 | $transitionInstance->setProcessInstanceId($execution->getProcessInstanceId()); |
||
270 | $transitionInstance->setProcessDefinitionId($execution->getProcessDefinitionId()); |
||
271 | $transitionInstance->setExecutionId($execution->getId()); |
||
272 | $transitionInstance->setActivityId($execution->getActivityId()); |
||
273 | |||
274 | $activity = $execution->getActivity(); |
||
275 | if ($activity !== null) { |
||
276 | $name = $activity->getName(); |
||
277 | if ($name === null) { |
||
278 | $name = $activity->getProperty("name"); |
||
279 | } |
||
280 | $transitionInstance->setActivityName($name); |
||
281 | $transitionInstance->setActivityType($activity->getProperty("type")); |
||
282 | } |
||
283 | |||
284 | $incidentIdList = $this->getIncidentIds($incidentsByExecution, $execution); |
||
285 | $incidents = $this->getIncidents($incidentsByExecution, $execution); |
||
286 | $transitionInstance->setIncidentIds($incidentIdList); |
||
287 | $transitionInstance->setIncidents($incidents); |
||
288 | |||
289 | return $transitionInstance; |
||
290 | } |
||
291 | |||
292 | protected function populateChildInstances( |
||
293 | array $activityInstances, |
||
294 | array $transitionInstances |
||
295 | ): void { |
||
296 | $childActivityInstances = []; |
||
297 | $childTransitionInstances = []; |
||
298 | |||
299 | foreach (array_values($activityInstances) as $instance) { |
||
300 | if ($instance->getParentActivityInstanceId() !== null) { |
||
301 | $key = $instance->getParentActivityInstanceId(); |
||
302 | $parentInstance = null; |
||
303 | if (array_key_exists($key, $activityInstances)) { |
||
304 | $parentInstance = $activityInstances[$key]; |
||
305 | } |
||
306 | if ($parentInstance === null) { |
||
307 | throw new ProcessEngineException("No parent activity instance with id " . $instance->getParentActivityInstanceId() . " generated"); |
||
308 | } |
||
309 | $this->putListElement($childActivityInstances, $parentInstance, $instance); |
||
310 | } |
||
311 | } |
||
312 | |||
313 | foreach (array_values($transitionInstances) as $instance) { |
||
314 | if ($instance->getParentActivityInstanceId() !== null) { |
||
315 | $key = $instance->getParentActivityInstanceId(); |
||
316 | $parentInstance = null; |
||
317 | if (array_key_exists($key, $activityInstances)) { |
||
318 | $parentInstance = $activityInstances[$key]; |
||
319 | } |
||
320 | if ($parentInstance === null) { |
||
321 | throw new ProcessEngineException("No parent activity instance with id " . $instance->getParentActivityInstanceId() . " generated"); |
||
322 | } |
||
323 | $this->putListElement($childTransitionInstances, $parentInstance, $instance); |
||
324 | } |
||
325 | } |
||
326 | |||
327 | foreach ($childActivityInstances as $pair) { |
||
328 | $instance = $pair[0]; |
||
329 | $childInstances = $pair[1]; |
||
330 | if (!empty($childInstances)) { |
||
331 | $instance->setChildActivityInstances($childInstances); |
||
332 | } |
||
333 | } |
||
334 | |||
335 | foreach ($childTransitionInstances as $pair) { |
||
336 | $instance = $pair[0]; |
||
337 | $childInstances = $pair[1]; |
||
338 | if (!empty($childTransitionInstances)) { |
||
339 | $instance->setChildTransitionInstances($childInstances); |
||
340 | } |
||
341 | } |
||
342 | } |
||
343 | |||
344 | protected function putListElement(array &$mapOfLists, $key, $listElement): void |
||
345 | { |
||
346 | $exists = false; |
||
347 | foreach ($mapOfLists as $idx => $pair) { |
||
348 | if ($pair[0] == $key) { |
||
349 | $exists = true; |
||
350 | $mapOfLists[$idx] = [$key, array_merge($pair[1], [$listElement])]; |
||
351 | break; |
||
352 | } |
||
353 | } |
||
354 | if (!$exists) { |
||
355 | $mapOfLists[] = [$key, [$listElement]]; |
||
356 | } |
||
357 | } |
||
358 | |||
359 | protected function filterProcessInstance(array $executionList): ExecutionEntity |
||
360 | { |
||
361 | foreach ($executionList as $execution) { |
||
362 | if ($execution->isProcessInstanceExecution()) { |
||
363 | return $execution; |
||
364 | } |
||
365 | } |
||
366 | throw new ProcessEngineException("Could not determine process instance execution"); |
||
367 | } |
||
368 | |||
369 | protected function filterLeaves(array $executionList): array |
||
370 | { |
||
371 | $leaves = []; |
||
372 | foreach ($executionList as $execution) { |
||
373 | // although executions executing throwing compensation events are not leaves in the tree, |
||
374 | // they are treated as leaves since their child executions are logical children of their parent scope execution |
||
375 | if (empty($execution->getNonEventScopeExecutions()) || CompensationBehavior::isCompensationThrowing($execution)) { |
||
376 | $leaves[] = $execution; |
||
377 | } |
||
378 | } |
||
379 | return $leaves; |
||
380 | } |
||
381 | |||
382 | protected function filterNonEventScopeExecutions(array $executionList): array |
||
383 | { |
||
384 | $nonEventScopeExecutions = []; |
||
385 | foreach ($executionList as $execution) { |
||
386 | if (!$execution->isEventScope()) { |
||
387 | $nonEventScopeExecutions[] = $execution; |
||
388 | } |
||
389 | } |
||
390 | return $nonEventScopeExecutions; |
||
391 | } |
||
392 | |||
393 | protected function loadProcessInstance(string $processInstanceId, CommandContext $commandContext): array |
||
394 | { |
||
395 | $result = null; |
||
396 | |||
397 | // first try to load from cache |
||
398 | // check whether the process instance is already (partially) loaded in command context |
||
399 | $cachedExecutions = $commandContext->getDbEntityManager()->getCachedEntitiesByType(ExecutionEntity::class); |
||
400 | foreach ($cachedExecutions as $executionEntity) { |
||
401 | if ($this->processInstanceId == $executionEntity->getProcessInstanceId()) { |
||
402 | // found one execution from process instance |
||
403 | $result = []; |
||
404 | $processInstance = $executionEntity->getProcessInstance(); |
||
405 | // add process instance |
||
406 | $result[] = $processInstance; |
||
407 | $this->loadChildExecutionsFromCache($processInstance, $result); |
||
408 | break; |
||
409 | } |
||
410 | } |
||
411 | |||
412 | if (empty($result)) { |
||
413 | // if the process instance could not be found in cache, load from database |
||
414 | $result = $this->loadFromDb($processInstanceId, $commandContext); |
||
415 | } |
||
416 | |||
417 | return $result; |
||
418 | } |
||
419 | |||
420 | protected function loadFromDb(string $processInstanceId, CommandContext $commandContext): array |
||
421 | { |
||
422 | $executions = $commandContext->getExecutionManager()->findExecutionsByProcessInstanceId($processInstanceId); |
||
423 | $processInstance = $commandContext->getExecutionManager()->findExecutionById($processInstanceId); |
||
424 | |||
425 | // initialize parent/child sets |
||
426 | if ($processInstance !== null) { |
||
427 | $processInstance->restoreProcessInstance($executions, null, null, null, null, null, null); |
||
428 | } |
||
429 | |||
430 | return $executions; |
||
431 | } |
||
432 | |||
433 | /** |
||
434 | * Loads all executions that are part of this process instance tree from the dbSqlSession cache. |
||
435 | * (optionally querying the db if a child is not already loaded. |
||
436 | * |
||
437 | * @param execution the current root execution (already contained in childExecutions) |
||
438 | * @param childExecutions the list in which all child executions should be collected |
||
439 | */ |
||
440 | protected function loadChildExecutionsFromCache(ExecutionEntity $execution, array &$childExecutions): void |
||
441 | { |
||
442 | $childrenOfThisExecution = $execution->getExecutions(); |
||
443 | if (!empty($childrenOfThisExecution)) { |
||
444 | $childExecutions = array_merge($childExecutions, $childrenOfThisExecution); |
||
445 | foreach ($childrenOfThisExecution as $child) { |
||
446 | $this->loadChildExecutionsFromCache($child, $childExecutions); |
||
447 | } |
||
448 | } |
||
449 | } |
||
450 | |||
451 | protected function groupIncidentIdsByExecutionId(CommandContext $commandContext): array |
||
459 | } |
||
460 | |||
461 | protected function getIncidentIds( |
||
477 | } |
||
478 | } |
||
479 | |||
480 | protected function getIncidents( |
||
481 | array $incidents, |
||
489 | } |
||
490 | } |
||
491 |