DbEntityManager   F
last analyzed

Complexity

Total Complexity 131

Size/Duplication

Total Lines 704
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 131
eloc 252
dl 0
loc 704
rs 2
c 0
b 0
f 0

64 Methods

Rating   Name   Duplication   Size   Complexity  
A getTableNamesPresentInDatabase() 0 3 1
A isHistoricByteArray() 0 7 2
A ensureHasId() 0 5 2
A invokeOptimisticLockingListeners() 0 15 5
A createHistoricVariableInstanceQuery() 0 3 1
B flushDbOperations() 0 52 6
A isDirty() 0 7 3
A createDbBulkOperation() 0 10 1
A createJobQuery() 0 3 1
A createHistoricDetailQuery() 0 3 1
A performBulkOperationPreserveOrder() 0 7 1
A initializeOperationManager() 0 3 1
A flushEntityCache() 0 5 2
A createDeploymentQuery() 0 3 1
A flushDbOperationManager() 0 29 5
A filterLoadedObjects() 0 14 5
A getCachedEntity() 0 3 1
A validateId() 0 3 1
A initializeEntityCache() 0 20 6
A updatePreserveOrder() 0 3 1
A createProcessDefinitionQuery() 0 3 1
A createHistoricProcessInstanceQuery() 0 3 1
A performEntityOperation() 0 7 1
A pruneDeletedEntities() 0 9 3
A flushEntity() 0 8 2
A getDbOperationManager() 0 3 1
A deletePreserveOrder() 0 3 1
A setIgnoreForeignKeysForNextFlush() 0 3 1
A performBulkOperation() 0 8 1
A selectBoolean() 0 7 2
A handleConcurrentModification() 0 18 5
A isDeleted() 0 3 1
A undoDelete() 0 3 1
B flushCachedEntity() 0 36 9
A createHistoricTaskInstanceQuery() 0 3 1
A createHistoricActivityInstanceQuery() 0 3 1
A selectById() 0 14 3
A canIgnoreHistoryModificationFailure() 0 5 3
A onEntityLoaded() 0 12 3
A createProcessInstanceQuery() 0 3 1
A merge() 0 14 2
A getDbEntityCache() 0 3 1
A createHistoricJobLogQuery() 0 3 1
A update() 0 3 1
A createGroupQuery() 0 3 1
A forceUpdate() 0 5 3
A setDbOperationManager() 0 3 1
A insert() 0 9 1
A lock() 0 3 1
A flush() 0 7 1
B selectList() 0 15 9
A createTaskQuery() 0 3 1
A createUserQuery() 0 3 1
A setDbEntityCache() 0 3 1
A contains() 0 3 1
A registerOptimisticLockingListener() 0 3 1
A cacheFilter() 0 7 2
A __construct() 0 9 2
A delete() 0 6 3
A selectListWithRawParameter() 0 7 3
A selectOne() 0 8 2
A getCachedEntitiesByType() 0 3 1
A createExecutionQuery() 0 3 1
A close() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like DbEntityManager 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 DbEntityManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Jabe\Impl\Db\EntityManager;
4
5
use Jabe\{
6
    OptimisticLockingException,
7
    ProcessEngineException
8
};
9
use Jabe\Impl\{
10
    DeploymentQueryImpl,
11
    ExecutionQueryImpl,
12
    GroupQueryImpl,
13
    HistoricActivityInstanceQueryImpl,
14
    HistoricDetailQueryImpl,
15
    HistoricJobLogQueryImpl,
16
    HistoricProcessInstanceQueryImpl,
17
    HistoricTaskInstanceQueryImpl,
18
    HistoricVariableInstanceQueryImpl,
19
    JobQueryImpl,
20
    Page,
21
    ProcessDefinitionQueryImpl,
22
    ProcessEngineLogger,
23
    ProcessInstanceQueryImpl,
24
    TaskQueryImpl,
25
    UserQueryImpl
26
};
27
use Jabe\Impl\Cfg\{
28
    IdGeneratorInterface,
29
    ProcessEngineConfigurationImpl
30
};
31
use Jabe\Impl\Context\Context;
32
use Jabe\Impl\Db\{
33
    DbEntityInterface,
34
    DbEntityLifecycleAwareInterface,
35
    EnginePersistenceLogger,
36
    EntityLoadListenerInterface,
37
    FlushResult,
38
    HistoricEntityInterface,
39
    ListQueryParameterObject,
40
    PersistenceSessionInterface
41
};
42
use Jabe\Impl\Db\EntityManager\Cache\{
43
    CachedDbEntity,
44
    DbEntityCache,
45
    DbEntityState
46
};
47
use Jabe\Impl\Db\EntityManager\Operation\{
48
    DbBulkOperation,
49
    DbEntityOperation,
50
    DbOperation,
51
    DbOperationState,
52
    DbOperationManager,
53
    DbOperationType
54
};
55
use Jabe\Impl\Identity\Db\{
56
    DbGroupQueryImpl,
57
    DbUserQueryImpl
58
};
59
use Jabe\Impl\Interceptor\SessionInterface;
60
use Jabe\Impl\Persistence\Entity\ByteArrayEntity;
61
use Jabe\Impl\Util\{
62
    CollectionUtil,
63
    EnsureUtil
64
};
65
use Jabe\Repository\ResourceTypes;
66
67
class DbEntityManager implements SessionInterface, EntityLoadListenerInterface
68
{
69
    //protected static final EnginePersistenceLogger LOG = ProcessEngineLogger.PERSISTENCE_LOGGER;
70
    public const TOGGLE_FOREIGN_KEY_STMT = "toggleForeignKey";
71
    public const BATCH_SIZE = 50;
72
73
    protected $optimisticLockingListeners = [];
74
75
    protected $idGenerator;
76
77
    protected $dbEntityCache;
78
79
    protected $dbOperationManager;
80
81
    protected $persistenceSession;
82
    protected $isIgnoreForeignKeysForNextFlush;
83
84
    public function __construct(IdGeneratorInterface $idGenerator, PersistenceSessionInterface $persistenceSession = null)
85
    {
86
        $this->idGenerator = $idGenerator;
87
        $this->persistenceSession = $persistenceSession;
88
        if ($this->persistenceSession !== null) {
89
            $this->persistenceSession->addEntityLoadListener($this);
90
        }
91
        $this->initializeEntityCache();
92
        $this->initializeOperationManager();
93
    }
94
95
    protected function initializeOperationManager(): void
96
    {
97
        $this->dbOperationManager = new DbOperationManager();
98
    }
99
100
    protected function initializeEntityCache(): void
101
    {
102
        $jobExecutorContext = Context::getJobExecutorContext();
103
        $processEngineConfiguration = Context::getProcessEngineConfiguration();
104
105
        if (
106
            $processEngineConfiguration !== null
107
            && $processEngineConfiguration->isDbEntityCacheReuseEnabled()
0 ignored issues
show
Bug introduced by
The method isDbEntityCacheReuseEnabled() 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

107
            && $processEngineConfiguration->/** @scrutinizer ignore-call */ isDbEntityCacheReuseEnabled()

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...
108
            && $jobExecutorContext !== null
109
        ) {
110
            $this->dbEntityCache = $jobExecutorContext->getEntityCache();
111
            if ($this->dbEntityCache === null) {
112
                $this->dbEntityCache = new DbEntityCache($processEngineConfiguration->getDbEntityCacheKeyMapping());
0 ignored issues
show
Bug introduced by
The method getDbEntityCacheKeyMapping() 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

112
                $this->dbEntityCache = new DbEntityCache($processEngineConfiguration->/** @scrutinizer ignore-call */ getDbEntityCacheKeyMapping());

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...
113
                $jobExecutorContext->setEntityCache($this->dbEntityCache);
114
            }
115
        } else {
116
            if ($processEngineConfiguration !== null) {
117
                $this->dbEntityCache = new DbEntityCache($processEngineConfiguration->getDbEntityCacheKeyMapping());
118
            } else {
119
                $this->dbEntityCache = new DbEntityCache();
120
            }
121
        }
122
    }
123
124
    // selects /////////////////////////////////////////////////
125
126
    public function selectList(string $statement, $parameter = null, $pageOrFirstResult = null, $maxResults = null): array
127
    {
128
        if ($parameter === null) {
129
            return $this->selectList($statement, null, 0, PHP_INT_MAX);
130
        } elseif ($parameter instanceof ListQueryParameterObject && $pageOrFirstResult === null) {
131
            return $this->selectListWithRawParameter($statement, $parameter, $parameter->getFirstResult(), $parameter->getMaxResults());
132
        } elseif ($pageOrFirstResult === null) {
133
            return $this->selectList($statement, $parameter, 0, PHP_INT_MAX);
134
        } elseif ($pageOrFirstResult instanceof Page) {
135
            if ($parameter instanceof ListQueryParameterObject) {
136
                return $this->selectList($statement, $parameter);
137
            }
138
            return $this->selectList($statement, $parameter, $pageOrFirstResult->getFirstResult(), $pageOrFirstResult->getMaxResults());
139
        } elseif (is_int($pageOrFirstResult) && is_int($maxResults)) {
140
            return $this->selectList($statement, new ListQueryParameterObject($parameter, $pageOrFirstResult, $maxResults));
141
        }
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...
142
    }
143
144
    public function selectListWithRawParameter(string $statement, $parameter, int $firstResult, int $maxResults): array
145
    {
146
        if ($firstResult == -1 ||  $maxResults == -1) {
147
            return [];
148
        }
149
        $loadedObjects = $this->persistenceSession->selectList($statement, $parameter);
0 ignored issues
show
Bug introduced by
The method selectList() 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

149
        /** @scrutinizer ignore-call */ 
150
        $loadedObjects = $this->persistenceSession->selectList($statement, $parameter);

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...
150
        return $this->filterLoadedObjects($loadedObjects);
151
    }
152
153
    public function selectOne(string $statement, $parameter)
154
    {
155
        $result = $this->persistenceSession->selectOne($statement, $parameter);
156
        if ($result instanceof DbEntityInterface) {
157
            $loadedObject = $result;
158
            $result = $this->cacheFilter($loadedObject);
159
        }
160
        return $result;
161
    }
162
163
    public function selectBoolean(string $statement, $parameter): bool
164
    {
165
        $result = $this->persistenceSession->selectList($statement, $parameter);
166
        if (!empty($result)) {
167
            return in_array(1, $result);
168
        }
169
        return false;
170
    }
171
172
    public function selectById(string $entityClass, string $id): ?DbEntityInterface
173
    {
174
        $persistentObject = $this->dbEntityCache->get($entityClass, $id);
175
        if (!empty($persistentObject)) {
176
            return $persistentObject;
177
        }
178
179
        $persistentObject = $this->persistenceSession->selectById($entityClass, $id);
180
181
        if (empty($persistentObject)) {
182
            return null;
183
        }
184
        // don't have to put object into the cache now. See onEntityLoaded() callback
185
        return $persistentObject;
186
    }
187
188
    public function getCachedEntity(string $type, string $id): ?DbEntityInterface
189
    {
190
        return $this->dbEntityCache->get($type, $id);
191
    }
192
193
    public function getCachedEntitiesByType(string $type): array
194
    {
195
        return $this->dbEntityCache->getEntitiesByType($type);
196
    }
197
198
    protected function filterLoadedObjects(array $loadedObjects): array
199
    {
200
        if (empty($loadedObjects) || $loadedObjects[0] === null) {
201
            return $loadedObjects;
202
        }
203
        if (!(is_a($loadedObjects[0], DbEntityInterface::class))) {
204
            return $loadedObjects;
205
        }
206
        $filteredObjects = [];
207
        foreach ($loadedObjects as $loadedObject) {
208
            $cachedPersistentObject = $this->cacheFilter($loadedObject);
209
            $filteredObjects[] = $cachedPersistentObject;
210
        }
211
        return $filteredObjects;
212
    }
213
214
    /** returns the object in the cache.  if this object was loaded before,
215
     * then the original object is returned. */
216
    protected function cacheFilter(DbEntityInterface $persistentObject): DbEntityInterface
217
    {
218
        $cachedPersistentObject = $this->dbEntityCache->get(get_class($persistentObject), $persistentObject->getId());
0 ignored issues
show
Bug introduced by
It seems like $persistentObject->getId() can also be of type null; however, parameter $id of Jabe\Impl\Db\EntityManag...he\DbEntityCache::get() 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

218
        $cachedPersistentObject = $this->dbEntityCache->get(get_class($persistentObject), /** @scrutinizer ignore-type */ $persistentObject->getId());
Loading history...
219
        if ($cachedPersistentObject !== null) {
220
            return $cachedPersistentObject;
221
        } else {
222
            return $persistentObject;
223
        }
224
    }
225
226
    public function onEntityLoaded(DbEntityInterface $entity): void
227
    {
228
        // we get a callback when the persistence session loads an object from the database
229
        $cachedPersistentObject = $this->dbEntityCache->get(entity->getClass(), entity->getId());
0 ignored issues
show
Bug introduced by
The constant Jabe\Impl\Db\EntityManager\entity was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
230
        if (empty($cachedPersistentObject)) {
231
            // only put into the cache if not already present
232
            $this->dbEntityCache->putPersistent($entity);
233
234
            // invoke postLoad() lifecycle method
235
            if ($entity instanceof DbEntityLifecycleAwareInterface) {
236
                $lifecycleAware = $entity;
237
                $lifecycleAware->postLoad();
238
            }
239
        }
240
    }
241
242
    public function lock(string $statement, $parameter = null): void
243
    {
244
        $this->persistenceSession->lock($statement, $parameter);
245
    }
246
247
    public function isDirty(DbEntityInterface $dbEntity): bool
248
    {
249
        $cachedEntity = $this->dbEntityCache->getCachedEntity($dbEntity);
250
        if ($cachedEntity === null) {
251
            return false;
252
        } else {
253
            return $cachedEntity->isDirty() || $cachedEntity->getEntityState() == DbEntityState::MERGED;
254
        }
255
    }
256
257
    public function flush(): void
258
    {
259
        // flush the entity cache which inserts operations to the db operation manager
260
        $this->flushEntityCache();
261
262
        // flush the db operation manager
263
        $this->flushDbOperationManager();
264
    }
265
266
    public function setIgnoreForeignKeysForNextFlush(bool $ignoreForeignKeysForNextFlush): void
267
    {
268
        $this->isIgnoreForeignKeysForNextFlush = $ignoreForeignKeysForNextFlush;
269
    }
270
271
    protected function flushDbOperationManager(): void
272
    {
273
274
        // obtain totally ordered operation list from operation manager
275
        $operationsToFlush = $this->dbOperationManager->calculateFlush();
276
        if (empty($operationsToFlush)) {
277
            return;
278
        }
279
280
        //LOG.databaseFlushSummary(operationsToFlush);
281
282
        // If we want to delete all table data as bulk operation, on tables which have self references,
283
        // We need to turn the foreign key check off on MySQL and MariaDB.
284
        // On other databases we have to do nothing, the mapped statement will be empty.
285
        if ($this->isIgnoreForeignKeysForNextFlush) {
286
            $this->persistenceSession->executeNonEmptyUpdateStmt(self::TOGGLE_FOREIGN_KEY_STMT, false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type array expected by parameter $params of Jabe\Impl\Db\Persistence...uteNonEmptyUpdateStmt(). ( Ignorable by Annotation )

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

286
            $this->persistenceSession->executeNonEmptyUpdateStmt(self::TOGGLE_FOREIGN_KEY_STMT, /** @scrutinizer ignore-type */ false);
Loading history...
287
            $this->persistenceSession->flushOperations();
288
        }
289
290
        try {
291
            $batches = CollectionUtil::partition($operationsToFlush, self::BATCH_SIZE);
292
            foreach ($batches as $batch) {
293
                $this->flushDbOperations($batch, $operationsToFlush);
294
            }
295
        } finally {
296
            if ($this->isIgnoreForeignKeysForNextFlush) {
297
                $this->persistenceSession->executeNonEmptyUpdateStmt(self::TOGGLE_FOREIGN_KEY_STMT, true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type array expected by parameter $params of Jabe\Impl\Db\Persistence...uteNonEmptyUpdateStmt(). ( Ignorable by Annotation )

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

297
                $this->persistenceSession->executeNonEmptyUpdateStmt(self::TOGGLE_FOREIGN_KEY_STMT, /** @scrutinizer ignore-type */ true);
Loading history...
298
                $this->persistenceSession->flushOperations();
299
                $this->isIgnoreForeignKeysForNextFlush = false;
300
            }
301
        }
302
    }
303
304
    protected function flushDbOperations(
305
        array &$operationsToFlush,
306
        array $allOperations
0 ignored issues
show
Unused Code introduced by
The parameter $allOperations is not used and could be removed. ( Ignorable by Annotation )

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

306
        /** @scrutinizer ignore-unused */ array $allOperations

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
307
    ): void {
308
        // execute the flush
309
        while (!empty($operationsToFlush)) {
310
            $flushResult = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $flushResult is dead and can be removed.
Loading history...
311
            try {
312
                $flushResult = $this->persistenceSession->executeDbOperations($operationsToFlush);
313
            } catch (\Exception $e) {
314
                // Top level persistence exception
315
                //throw LOG.flushDbOperationUnexpectedException(allOperations, e);
316
                throw new \Exception("flushDbOperationUnexpectedException");
317
            }
318
319
            $failedOperations = $flushResult->getFailedOperations();
320
321
            foreach ($failedOperations as $failedOperation) {
322
                $failureState = $failedOperation->getState();
323
324
                if ($failureState == DbOperationState::FAILED_CONCURRENT_MODIFICATION) {
325
                    // this method throws an exception in case the flush cannot be continued;
326
                    // accordingly, this method will be left as well in this case
327
                    $this->handleConcurrentModification($failedOperation);
328
                } elseif ($failureState == DbOperationState::FAILED_ERROR) {
329
                    // Top level persistence exception
330
                    $failure = $failedOperation->getFailure();
0 ignored issues
show
Unused Code introduced by
The assignment to $failure is dead and can be removed.
Loading history...
331
                    //throw LOG.flushDbOperationException(allOperations, failedOperation, failure);
332
                    throw new \Exception("flushDbOperationException");
333
                } else {
334
                    // This branch should never be reached and the exception thus indicates a bug
335
                    throw new ProcessEngineException(
336
                        "Entity session returned a failed operation not " .
337
                        "in an error state. This indicates a bug"
338
                    );
339
                }
340
                /*elseif ($failureState == DbOperationState::FAILED_CONCURRENT_MODIFICATION_CRDB) {
341
                    $this->handleConcurrentModificationCrdb($failedOperation);
342
                }*/
343
            }
344
345
            $remainingOperations = $flushResult->getRemainingOperations();
346
347
            // avoid infinite loops
348
            EnsureUtil::ensureLessThan(
349
                "Database flush did not process any operations. This indicates a bug.",
350
                "remainingOperations",
351
                count($remainingOperations),
352
                count($operationsToFlush)
353
            );
354
355
            $operationsToFlush = $remainingOperations;
356
        }
357
    }
358
359
    public function flushEntity(DbEntityInterface $entity): void
360
    {
361
        $cachedEntity = $this->dbEntityCache->getCachedEntity($entity);
362
        if ($cachedEntity !== null) {
363
            $this->flushCachedEntity($cachedEntity);
364
        }
365
366
        $this->flushDbOperationManager();
367
    }
368
369
    /**
370
     * Decides if an operation that failed for concurrent modifications can be tolerated,
371
     * or if OptimisticLockingException should be raised
372
     *
373
     * @param dbOperation
374
     * @throws OptimisticLockingException if there is no handler for the failure
375
     */
376
    protected function handleConcurrentModification(DbOperation $dbOperation): void
377
    {
378
        $handlingResult = $this->invokeOptimisticLockingListeners($dbOperation);
379
380
        if (
381
            OptimisticLockingResult::THROW == $handlingResult
0 ignored issues
show
introduced by
The condition Jabe\Impl\Db\EntityManag...HROW == $handlingResult is always false.
Loading history...
382
            && canIgnoreHistoryModificationFailure($dbOperation)
0 ignored issues
show
Bug introduced by
The function canIgnoreHistoryModificationFailure was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

382
            && /** @scrutinizer ignore-call */ canIgnoreHistoryModificationFailure($dbOperation)
Loading history...
383
        ) {
384
            $handlingResult = OptimisticLockingResult::IGNORE;
385
        }
386
387
        switch ($handlingResult) {
388
            case OptimisticLockingResult::IGNORE:
389
                break;
390
            case OptimisticLockingResult::THROW:
391
            default:
392
                //throw LOG.concurrentUpdateDbEntityException(dbOperation);
393
                throw new \Exception("concurrentUpdateDbEntityException");
394
        }
395
    }
396
397
    /*protected function handleConcurrentModificationCrdb(DbOperation $dbOperation): void
398
    {
399
        $handlingResult = $this->invokeOptimisticLockingListeners($dbOperation);
400
401
        if (OptimisticLockingResult::IGNORE == $handlingResult) {
402
            //LOG.crdbFailureIgnored(dbOperation);
403
        }
404
405
        // CRDB concurrent modification exceptions always lead to the transaction
406
        // being aborted, so we must always throw an exception.
407
        //throw LOG.crdbTransactionRetryException(dbOperation);
408
        //throw new \Exception("crdbTransactionRetryException");
409
    }*/
410
411
    private function invokeOptimisticLockingListeners(DbOperation $dbOperation): OptimisticLockingResult
412
    {
413
        $handlingResult = OptimisticLockingResult::THROW;
414
415
        if (!empty($this->optimisticLockingListeners)) {
416
            foreach ($this->optimisticLockingListeners as $optimisticLockingListener) {
417
                if (
418
                    $optimisticLockingListener->getEntityType() === null
419
                    || is_a($optimisticLockingListener->getEntityType(), $dbOperation->getEntityType(), true)
0 ignored issues
show
Bug introduced by
It seems like $dbOperation->getEntityType() can also be of type null; however, parameter $class of is_a() 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

419
                    || is_a($optimisticLockingListener->getEntityType(), /** @scrutinizer ignore-type */ $dbOperation->getEntityType(), true)
Loading history...
420
                ) {
421
                    $handlingResult = $optimisticLockingListener->failedOperation($dbOperation);
422
                }
423
            }
424
        }
425
        return $handlingResult;
426
    }
427
428
    /**
429
     * Determines if a failed database operation (OptimisticLockingException)
430
     * on a Historic entity can be ignored.
431
     *
432
     * @param dbOperation that failed
433
     * @return bool true if the failure can be ignored
434
     */
435
    protected function canIgnoreHistoryModificationFailure(DbOperation $dbOperation): bool
436
    {
437
        $dbEntity = $dbOperation->getEntity();
0 ignored issues
show
Bug introduced by
The method getEntity() does not exist on Jabe\Impl\Db\EntityManager\Operation\DbOperation. Did you maybe mean getEntityType()? ( Ignorable by Annotation )

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

437
        /** @scrutinizer ignore-call */ 
438
        $dbEntity = $dbOperation->getEntity();

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...
438
        return Context::getProcessEngineConfiguration()->isSkipHistoryOptimisticLockingExceptions()
439
                && ($dbEntity instanceof HistoricEntityInterface || $this->isHistoricByteArray($dbEntity));
440
    }
441
442
    protected function isHistoricByteArray(DbEntityInterface $dbEntity): bool
443
    {
444
        if ($dbEntity instanceof ByteArrayEntity) {
445
            $byteArrayEntity = $dbEntity;
446
            return $byteArrayEntity->getType() == ResourceTypes::history()->getValue();
447
        } else {
448
            return false;
449
        }
450
    }
451
452
    /**
453
     * Flushes the entity cache:
454
     * Depending on the entity state, the required DbOperation is performed and the cache is updated.
455
     */
456
    protected function flushEntityCache(): void
457
    {
458
        $cachedEntities = $this->dbEntityCache->getCachedEntities();
459
        foreach ($cachedEntities as $cachedDbEntity) {
460
            $this->flushCachedEntity($cachedDbEntity);
461
        }
462
463
        // log cache state after flush
464
        //LOG.flushedCacheState(dbEntityCache->getCachedEntities());
465
    }
466
467
    protected function flushCachedEntity(CachedDbEntityInterface $cachedDbEntity): void
468
    {
469
        if ($cachedDbEntity->getEntityState() == DbEntityState::TRANSIENT) {
470
            // latest state of references in cache is relevant when determining insertion order
471
            $cachedDbEntity->determineEntityReferences();
472
            // perform DbOperationType::INSERT
473
            $this->performEntityOperation($cachedDbEntity, DbOperationType::INSERT);
474
            // mark DbEntityState::PERSISTENT
475
            $cachedDbEntity->setEntityState(DbEntityState::PERSISTENT);
476
        } elseif ($cachedDbEntity->getEntityState() == DbEntityState::PERSISTENT && $cachedDbEntity->isDirty()) {
477
            // object is dirty -> perform UPDATE
478
            $this->performEntityOperation($cachedDbEntity, DbOperationType::UPDATE);
479
        } elseif ($cachedDbEntity->getEntityState() == DbEntityState::MERGED) {
480
            // perform UPDATE
481
            $this->performEntityOperation($cachedDbEntity, DbOperationType::UPDATE);
482
            // mark DbEntityState::PERSISTENT
483
            $cachedDbEntity->setEntityState(DbEntityState::PERSISTENT);
484
        } elseif ($cachedDbEntity->getEntityState() == DbEntityState::DELETED_TRANSIENT) {
485
            // remove from cache
486
            $this->dbEntityCache->remove($cachedDbEntity);
487
        } elseif (
488
            $cachedDbEntity->getEntityState() == DbEntityState::DELETED_PERSISTENT
489
            || $cachedDbEntity->getEntityState() == DbEntityState::DELETED_MERGED
490
        ) {
491
            // perform DbOperationType::DELETE
492
            $this->performEntityOperation($cachedDbEntity, DbOperationType::DELETE);
493
            // remove from cache
494
            $this->dbEntityCache->remove($cachedDbEntity);
495
        }
496
497
        // if object is DbEntityState::PERSISTENT after flush
498
        if ($cachedDbEntity->getEntityState() == DbEntityState::PERSISTENT) {
499
            // make a new copy
500
            $cachedDbEntity->makeCopy();
501
            // update cached references
502
            $cachedDbEntity->determineEntityReferences();
503
        }
504
    }
505
506
    public function insert(DbEntityInterface $dbEntity): void
507
    {
508
        // generate Id if not present
509
        $this->ensureHasId($dbEntity);
510
511
        $this->validateId($dbEntity);
512
513
        // put into cache
514
        $this->dbEntityCache->putTransient($dbEntity);
515
    }
516
517
    public function merge(DbEntityInterface $dbEntity): void
518
    {
519
520
        if ($dbEntity->getId() === null) {
521
            //throw LOG.mergeDbEntityException(dbEntity);
522
            throw new \Exception("mergeDbEntityException");
523
        }
524
525
        // NOTE: a proper implementation of merge() would fetch the entity from the database
526
        // and merge the state changes. For now, we simply always perform an update.
527
        // Supposedly, the "proper" implementation would reduce the number of situations where
528
        // optimistic locking results in a conflict.
529
530
        $this->dbEntityCache->putMerged($dbEntity);
531
    }
532
533
    public function forceUpdate(DbEntityInterface $entity): void
534
    {
535
        $cachedEntity = $this->dbEntityCache->getCachedEntity($entity);
536
        if ($cachedEntity !== null && $cachedEntity->getEntityState() == DbEntityState::PERSISTENT) {
537
            $cachedEntity->forceSetDirty();
538
        }
539
    }
540
541
    public function undoDelete(DbEntityInterface $entity): void
542
    {
543
        $this->dbEntityCache->undoDelete($entity);
544
    }
545
546
    public function update(string $entityType, string $statement, $parameter): void
547
    {
548
        $this->performBulkOperation($entityType, $statement, $parameter, DbOperationType::UPDATE_BULK);
549
    }
550
551
    /**
552
     * Several update operations added by this method will be executed preserving the order of method calls, no matter what entity type they refer to.
553
     * They will though be executed after all "not-bulk" operations (e.g. DbEntityManager#insert(DbEntity) or DbEntityManager#merge(DbEntity))
554
     * and after those updates added by {@link DbEntityManager#update(Class, String, Object)}.
555
     * @param entityType
556
     * @param statement
557
     * @param parameter
558
     */
559
    public function updatePreserveOrder(string $entityType, string $statement, $parameter): void
560
    {
561
        $this->performBulkOperationPreserveOrder($entityType, $statement, $parameter, DbOperationType::UPDATE_BULK);
562
    }
563
564
    public function delete($entityTypeOrEntity, string $statement = null, $parameter = null): void
565
    {
566
        if (is_string($entityTypeOrEntity)) {
567
            $this->performBulkOperation($entityTypeOrEntity, $statement, $parameter, DbOperationType::DELETE_BULK);
0 ignored issues
show
Bug introduced by
It seems like $statement can also be of type null; however, parameter $statement of Jabe\Impl\Db\EntityManag...:performBulkOperation() 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

567
            $this->performBulkOperation($entityTypeOrEntity, /** @scrutinizer ignore-type */ $statement, $parameter, DbOperationType::DELETE_BULK);
Loading history...
568
        } elseif ($entityTypeOrEntity instanceof DbEntityInterface) {
569
            $this->dbEntityCache->setDeleted($entityTypeOrEntity);
570
        }
571
    }
572
573
    /**
574
     * Several delete operations added by this method will be executed preserving the order of method calls, no matter what entity type they refer to.
575
     * They will though be executed after all "not-bulk" operations (e.g. DbEntityManager#insert(DbEntity) or DbEntityManager#merge(DbEntity))
576
     * and after those deletes added by {@link DbEntityManager#delete(Class, String, Object)}.
577
     * @param entityType
578
     * @param statement
579
     * @param parameter
580
     * @return delete operation
581
     */
582
    public function deletePreserveOrder(string $entityType, string $statement, $parameter): DbBulkOperation
583
    {
584
        return $this->performBulkOperationPreserveOrder($entityType, $statement, $parameter, DbOperationType::DELETE_BULK);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->performBul...ationType::DELETE_BULK) returns the type Jabe\Impl\Db\EntityManag...eration\DbBulkOperation which is incompatible with the documented return type Jabe\Impl\Db\EntityManager\delete.
Loading history...
585
    }
586
587
    protected function performBulkOperation(string $entityType, string $statement, $parameter, string $operationType): DbBulkOperation
588
    {
589
        // create operation
590
        $bulkOperation = $this->createDbBulkOperation($entityType, $statement, $parameter, $operationType);
591
592
        // schedule operation
593
        $this->dbOperationManager->addOperation($bulkOperation);
0 ignored issues
show
Bug introduced by
$bulkOperation of type Jabe\Impl\Db\EntityManag...eration\DbBulkOperation is incompatible with the type Jabe\Impl\Db\EntityManag...ation\DbEntityOperation expected by parameter $newOperation of Jabe\Impl\Db\EntityManag...Manager::addOperation(). ( Ignorable by Annotation )

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

593
        $this->dbOperationManager->addOperation(/** @scrutinizer ignore-type */ $bulkOperation);
Loading history...
594
        return $bulkOperation;
595
    }
596
597
    protected function performBulkOperationPreserveOrder(string $entityType, string $statement, $parameter, string $operationType): DbBulkOperation
598
    {
599
        $bulkOperation = $this->createDbBulkOperation($entityType, $statement, $parameter, $operationType);
600
601
        // schedule operation
602
        $this->dbOperationManager->addOperationPreserveOrder($bulkOperation);
603
        return $bulkOperation;
604
    }
605
606
    private function createDbBulkOperation(string $entityType, string $statement, $parameter, string $operationType): DbBulkOperation
607
    {
608
        // create operation
609
        $bulkOperation = new DbBulkOperation();
0 ignored issues
show
Bug introduced by
The call to Jabe\Impl\Db\EntityManag...peration::__construct() has too few arguments starting with operationType. ( Ignorable by Annotation )

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

609
        $bulkOperation = /** @scrutinizer ignore-call */ new DbBulkOperation();

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...
610
        // configure operation
611
        $bulkOperation->setOperationType($operationType);
612
        $bulkOperation->setEntityType($entityType);
613
        $bulkOperation->setStatement($statement);
614
        $bulkOperation->setParameter($parameter);
615
        return $bulkOperation;
616
    }
617
618
    protected function performEntityOperation(CachedDbEntityInterface $cachedDbEntity, string $type): void
619
    {
620
        $dbOperation = new DbEntityOperation();
621
        $dbOperation->setEntity($cachedDbEntity->getEntity());
622
        $dbOperation->setFlushRelevantEntityReferences($cachedDbEntity->getFlushRelevantEntityReferences());
623
        $dbOperation->setOperationType($type);
624
        $this->dbOperationManager->addOperation($dbOperation);
625
    }
626
627
    public function close(): void
628
    {
629
    }
630
631
    public function isDeleted(DbEntityInterface $object): bool
632
    {
633
        return $this->dbEntityCache->isDeleted($object);
634
    }
635
636
    protected function ensureHasId(DbEntityInterface $dbEntity): void
637
    {
638
        if ($dbEntity->getId() === null) {
639
            $nextId = $this->idGenerator->getNextId();
640
            $dbEntity->setId($nextId);
641
        }
642
    }
643
644
    protected function validateId(DbEntityInterface $dbEntity): void
645
    {
646
        EnsureUtil::ensureValidIndividualResourceId("Entity " . $dbEntity . " has an invalid id", $dbEntity->getId());
0 ignored issues
show
Bug introduced by
Are you sure $dbEntity of type Jabe\Impl\Db\DbEntityInterface 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

646
        EnsureUtil::ensureValidIndividualResourceId("Entity " . /** @scrutinizer ignore-type */ $dbEntity . " has an invalid id", $dbEntity->getId());
Loading history...
Bug introduced by
It seems like $dbEntity->getId() can also be of type null; however, parameter $id of Jabe\Impl\Util\EnsureUti...dIndividualResourceId() 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

646
        EnsureUtil::ensureValidIndividualResourceId("Entity " . $dbEntity . " has an invalid id", /** @scrutinizer ignore-type */ $dbEntity->getId());
Loading history...
647
    }
648
649
    public function pruneDeletedEntities(array $listToPrune): array
650
    {
651
        $prunedList = [];
652
        foreach ($listToPrune as $potentiallyDeleted) {
653
            if (!$this->isDeleted($potentiallyDeleted)) {
654
                $prunedList[] = $potentiallyDeleted;
655
            }
656
        }
657
        return $prunedList;
658
    }
659
660
    public function contains(DbEntityInterface $dbEntity): bool
661
    {
662
        return $this->dbEntityCache->contains($dbEntity);
663
    }
664
665
    // getters / setters /////////////////////////////////
666
667
    public function getDbOperationManager(): DbOperationManager
668
    {
669
        return $this->dbOperationManager;
670
    }
671
672
    public function setDbOperationManager(DbOperationManager $operationManager): void
673
    {
674
        $this->dbOperationManager = $operationManager;
675
    }
676
677
    public function getDbEntityCache(): DbEntityCache
678
    {
679
        return $this->dbEntityCache;
680
    }
681
682
    public function setDbEntityCache(DbEntityCache $dbEntityCache): void
683
    {
684
        $this->dbEntityCache = $dbEntityCache;
685
    }
686
687
    // query factory methods ////////////////////////////////////////////////////
688
689
    public function createDeploymentQuery(): DeploymentQueryImpl
690
    {
691
        return new DeploymentQueryImpl();
0 ignored issues
show
Bug introduced by
The call to Jabe\Impl\DeploymentQueryImpl::__construct() has too few arguments starting with commandExecutor. ( Ignorable by Annotation )

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

691
        return /** @scrutinizer ignore-call */ new DeploymentQueryImpl();

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...
692
    }
693
694
    public function createProcessDefinitionQuery(): ProcessDefinitionQueryImpl
695
    {
696
        return new ProcessDefinitionQueryImpl();
697
    }
698
699
    /*public CaseDefinitionQueryImpl createCaseDefinitionQuery() {
700
        return new CaseDefinitionQueryImpl();
701
    }*/
702
703
    public function createProcessInstanceQuery(): ProcessInstanceQueryImpl
704
    {
705
        return new ProcessInstanceQueryImpl();
706
    }
707
708
    public function createExecutionQuery(): ExecutionQueryImpl
709
    {
710
        return new ExecutionQueryImpl();
0 ignored issues
show
Bug introduced by
The call to Jabe\Impl\ExecutionQueryImpl::__construct() has too few arguments starting with commandExecutor. ( Ignorable by Annotation )

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

710
        return /** @scrutinizer ignore-call */ new ExecutionQueryImpl();

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...
711
    }
712
713
    public function createTaskQuery(): TaskQueryImpl
714
    {
715
        return new TaskQueryImpl();
716
    }
717
718
    public function createJobQuery(): JobQueryImpl
719
    {
720
        return new JobQueryImpl();
0 ignored issues
show
Bug introduced by
The call to Jabe\Impl\JobQueryImpl::__construct() has too few arguments starting with commandExecutor. ( Ignorable by Annotation )

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

720
        return /** @scrutinizer ignore-call */ new JobQueryImpl();

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...
721
    }
722
723
    public function createHistoricProcessInstanceQuery(): HistoricProcessInstanceQueryImpl
724
    {
725
        return new HistoricProcessInstanceQueryImpl();
726
    }
727
728
    public function createHistoricActivityInstanceQuery(): HistoricActivityInstanceQueryImpl
729
    {
730
        return new HistoricActivityInstanceQueryImpl();
731
    }
732
733
    public function createHistoricTaskInstanceQuery(): HistoricTaskInstanceQueryImpl
734
    {
735
        return new HistoricTaskInstanceQueryImpl();
0 ignored issues
show
Bug introduced by
The call to Jabe\Impl\HistoricTaskIn...ueryImpl::__construct() has too few arguments starting with commandExecutor. ( Ignorable by Annotation )

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

735
        return /** @scrutinizer ignore-call */ new HistoricTaskInstanceQueryImpl();

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...
736
    }
737
738
    public function createHistoricDetailQuery(): HistoricDetailQueryImpl
739
    {
740
        return new HistoricDetailQueryImpl();
741
    }
742
743
    public function createHistoricVariableInstanceQuery(): HistoricVariableInstanceQueryImpl
744
    {
745
        return new HistoricVariableInstanceQueryImpl();
746
    }
747
748
    public function createHistoricJobLogQuery(): HistoricJobLogQueryImpl
749
    {
750
        return new HistoricJobLogQueryImpl();
0 ignored issues
show
Bug introduced by
The call to Jabe\Impl\HistoricJobLogQueryImpl::__construct() has too few arguments starting with commandExecutor. ( Ignorable by Annotation )

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

750
        return /** @scrutinizer ignore-call */ new HistoricJobLogQueryImpl();

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...
751
    }
752
753
    public function createUserQuery(): UserQueryImpl
754
    {
755
        return new DbUserQueryImpl();
756
    }
757
758
    public function createGroupQuery(): GroupQueryImpl
759
    {
760
        return new DbGroupQueryImpl();
761
    }
762
763
    public function registerOptimisticLockingListener(OptimisticLockingListenerInterface $optimisticLockingListener = null): void
764
    {
765
        $this->optimisticLockingListeners[] = $optimisticLockingListener;
766
    }
767
768
    public function getTableNamesPresentInDatabase(): array
769
    {
770
        return $this->persistenceSession->getTableNamesPresent();
771
    }
772
}
773