Test Failed
Push — main ( c7561b...fa446d )
by Bingo
15:52
created

DbOperationManager::addSortedModifications()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 23
c 0
b 0
f 0
rs 9.8666
cc 4
nc 6
nop 1
1
<?php
2
3
namespace Jabe\Engine\Impl\Db\EntityManager\Operation;
4
5
use Jabe\Engine\Impl\Db\{
6
    DbEntityInterface,
7
    HasDbReferencesInterface
8
};
9
10
class DbOperationManager
11
{
12
    // comparators ////////////////
13
14
    public $inserts = [];
15
16
    /** UPDATEs of a single entity */
17
    public $updates = [];
18
19
    /** DELETEs of a single entity */
20
    public $deletes = [];
21
22
    /** bulk modifications (DELETE, UPDATE) on an entity collection */
23
    public $bulkOperations = [];
24
25
    /** bulk modifications (DELETE, UPDATE) for which order of execution is important */
26
    public $bulkOperationsInsertionOrder = [];
27
28
    public function addOperation(DbEntityOperation $newOperation): bool
29
    {
30
        if ($newOperation instanceof DbEntityOperation) {
0 ignored issues
show
introduced by
$newOperation is always a sub-type of Jabe\Engine\Impl\Db\Enti...ation\DbEntityOperation.
Loading history...
31
            $clazz = get_class($newOperation);
32
            if ($newOperation->getOperationType() == DbOperationType::INSERT) {
33
                if (!array_key_exists($clazz, $this->inserts)) {
34
                    $this->inserts[$clazz] = [];
35
                }
36
                if (!in_array($newOperation, $this->inserts[$clazz])) {
37
                    $this->inserts[$clazz][] = $newOperation;
38
                    return true;
39
                }
40
            } elseif ($newOperation->getOperationType() == DbOperationType::DELETE) {
41
                if (!array_key_exists($clazz, $this->deletes)) {
42
                    $this->deletes[$clazz] = [];
43
                }
44
                if (!in_array($newOperation, $this->deletes)) {
45
                    $this->deletes[$clazz][] = $newOperation;
46
                    return true;
47
                }
48
            } else {// UPDATE
49
                if (!array_key_exists($clazz, $this->updates)) {
50
                    $this->updates[$clazz] = [];
51
                }
52
                if (!in_array($newOperation, $this->updates)) {
53
                    $this->updates[$clazz][] = $newOperation;
54
                    return true;
55
                }
56
            }
57
        } elseif ($newOperation instanceof DbBulkOperation) {
58
            if (!array_key_exists($clazz, $this->bulkOperations)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $clazz seems to be never defined.
Loading history...
59
                $this->bulkOperations[$clazz] = [];
60
            }
61
            if (!in_array($newOperation, $this->bulkOperations)) {
62
                $this->bulkOperations[$clazz][] = $newOperation;
63
                return true;
64
            }
65
        }
66
        return false;
67
    }
68
69
    public function addOperationPreserveOrder(DbBulkOperation $newOperation): bool
70
    {
71
        if (!in_array($newOperation, $this->bulkOperationsInsertionOrder)) {
72
            $this->bulkOperationsInsertionOrder[] = $newOperation;
73
            return true;
74
        }
75
        return false;
76
    }
77
78
    public function calculateFlush(): array
79
    {
80
        $flush = [];
81
        // first INSERTs
82
        $this->addSortedInserts($flush);
83
        // then UPDATEs + DELETEs
84
        $this->addSortedModifications($flush);
85
        $this->determineDependencies($flush);
86
        return $flush;
87
    }
88
89
    /** Adds the insert operations to the flush (in correct order).
90
     * @param operationsForFlush */
91
    protected function addSortedInserts(array &$flush): void
92
    {
93
        foreach ($this->inserts as $clazz => $operationsForType) {
94
            // add inserts to flush
95
            if (is_a($clazz, HasDbReferencesInterface::class, true)) {
96
                // if this type has self references, we need to resolve the reference order
97
                $flush = array_merge($flush, $this->sortByReferences($operationsForType));
98
            } else {
99
                $flush = array_merge($flush, $operationsForType);
100
            }
101
        }
102
    }
103
104
    /** Adds a correctly ordered list of UPDATE and DELETE operations to the flush.
105
     * @param flush */
106
    protected function addSortedModifications(array &$flush): void
107
    {
108
        // calculate sorted set of all modified entity types
109
        $modifiedEntityTypes = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $modifiedEntityTypes is dead and can be removed.
Loading history...
110
        $modifiedEntityTypes = array_keys($this->updates);
111
        $modifiedEntityTypes = array_merge($modifiedEntityTypes, array_keys($this->deletes));
112
        $modifiedEntityTypes = array_merge($modifiedEntityTypes, array_keys($this->bulkOperations));
113
114
        foreach ($modifiedEntityTypes as $type) {
115
            // first perform entity UPDATES
116
            $this->addSortedModificationsForType($type, $this->updates[$type], $flush);
117
            // next perform entity DELETES
118
            $this->addSortedModificationsForType($type, $this->deletes[$type], $flush);
119
            // last perform bulk operations
120
            if (array_key_exists($type, $this->bulkOperations)) {
121
                $bulkOperationsForType = $this->bulkOperations[$type];
122
                $flush = array_merge($flush, $bulkOperationsForType);
123
            }
124
        }
125
126
        //the very last perform bulk operations for which the order is important
127
        if (!empty($this->bulkOperationsInsertionOrder)) {
128
            $flush = array_merge($flush, $this->bulkOperationsInsertionOrder);
129
        }
130
    }
131
132
    protected function addSortedModificationsForType(string $type, array $preSortedOperations, array &$flush): void
133
    {
134
        if (!empty($preSortedOperations)) {
135
            if (is_a($type, HasDbReferencesInterface::class, true)) {
136
                // if this type has self references, we need to resolve the reference order
137
                $flush = array_merge($flush, $this->sortByReferences($preSortedOperations));
138
            } else {
139
                $flush = array_merge($flush, $preSortedOperations);
140
            }
141
        }
142
    }
143
144
    /**
145
     * Assumptions:
146
     * a) all operations in the set work on entities such that the entities implement {@link HasDbReferences}.
147
     * b) all operations in the set work on the same type (ie. all operations are INSERTs or DELETEs).
148
     *
149
     */
150
    protected function sortByReferences(array $preSorted): array
151
    {
152
        // copy the pre-sorted set and apply final sorting to list
153
        $opList = $preSorted;
154
155
        for ($i = 0; $i < count($opList); $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...
156
            $currentOperation = $opList[$i];
157
            $currentEntity = $currentOperation->getEntity();
158
            $currentReferences = $currentOperation->getFlushRelevantEntityReferences();
159
160
            // check whether this operation must be placed after another operation
161
            $moveTo = $i;
162
            for ($k = $i + 1; $k < count($opList); $k += 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...
163
                $otherOperation = $opList[$k];
164
                $otherEntity = $otherOperation->getEntity();
165
                $otherReferences = $otherOperation->getFlushRelevantEntityReferences();
166
167
                if ($currentOperation->getOperationType() == DbOperationType::INSERT) {
168
                    // if we reference the other entity, we need to be inserted after that entity
169
                    if (!empty($currentReferences) && in_array($otherEntity->getId(), $currentReferences)) {
170
                        $moveTo = $k;
171
                        break; // we can only reference a single entity
172
                    }
173
                } else { // UPDATE or DELETE
174
                    // if the other entity has a reference to us, we must be placed after the other entity
175
                    if (!empty($otherReferences) && in_array($currentEntity->getId(), $otherReferences)) {
176
                        $moveTo = $k;
177
                        // cannot break, there may be another entity further to the right which also references us
178
                    }
179
                }
180
            }
181
182
            if ($moveTo > $i) {
183
                unset($opList[$i]);
184
                $opList[$moveTo] = $currentOperation;
185
                $i -= 1;
186
            }
187
        }
188
189
        return $opList;
190
    }
191
192
    protected function determineDependencies(array $flush): void
193
    {
194
        $defaultValue = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $defaultValue is dead and can be removed.
Loading history...
195
        foreach ($flush as $operation) {
196
            if ($operation instanceof DbEntityOperation) {
197
                $entity = $operation->getEntity();
198
                if ($entity instanceof HasDbReferencesInterface) {
199
                    $dependentEntities = $entity->getDependentEntities();
200
                    if (!empty($dependentEntities)) {
201
                        foreach ($dependentEntities as $id => $type) {
202
                            if (array_key_exists($type, $this->deletes)) {
203
                                foreach ($this->deletes[$type] as $o) {
204
                                    if ($id == $o->getEntity()->getId()) {
205
                                        $o->setDependency($operation);
206
                                    }
207
                                }
208
                            }
209
                        }
210
                    }
211
                }
212
            }
213
        }
214
    }
215
}
216