Pool::updateTuple()   B
last analyzed

Complexity

Conditions 11
Paths 97

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 11.0699

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 14
c 1
b 0
f 0
nc 97
nop 6
dl 0
loc 22
rs 7.3166
ccs 11
cts 12
cp 0.9167
crap 11.0699

How to fix   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
declare(strict_types=1);
4
5
namespace Cycle\ORM\Transaction;
6
7
use Cycle\ORM\Exception\PoolException;
8
use Cycle\ORM\Heap\Node;
9
use Cycle\ORM\Heap\State;
10
use Cycle\ORM\ORMInterface;
11
use Cycle\ORM\Reference\ReferenceInterface;
12
use JetBrains\PhpStorm\ExpectedValues;
13
14
/**
15
 * @internal
16
 *
17
 * @psalm-suppress TypeDoesNotContainType
18
 * @psalm-suppress RedundantCondition
19
 */
20
final class Pool implements \Countable
21
{
22
    private TupleStorage $storage;
23
    private TupleStorage $all;
24
    private TupleStorage $priorityStorage;
25
26
    /**
27
     * @var Tuple[]
28
     *
29
     * @psalm-var list<Tuple>
30
     */
31
    private array $unprocessed;
32
33
    private bool $priorityEnabled = false;
34
    private bool $priorityAutoAttach = false;
35
    private int $happens = 0;
36
37
    /**
38
     * Indicates that Pool is now iterating
39
     */
40
    private bool $iterating = false;
41
42
    public function __construct(
43
        private ORMInterface $orm,
44
    ) {
45
        $this->storage = new TupleStorage();
46
        $this->all = new TupleStorage();
47 2918
    }
48
49
    public function someHappens(): void
50 2918
    {
51 2918
        ++$this->happens;
52
    }
53
54 2564
    public function attach(
55
        object $entity,
56 2564
        #[ExpectedValues(values: [Tuple::TASK_STORE, Tuple::TASK_DELETE, Tuple::TASK_FORCE_DELETE])]
57
        int $task,
58
        bool $cascade,
59 2918
        ?Node $node = null,
60
        ?State $state = null,
61
        ?int $status = null,
62
        bool $highPriority = false,
63
        bool $persist = false,
64
    ): Tuple {
65
        // Find existing
66
        $tuple = $this->offsetGet($entity);
67
        if ($tuple !== null) {
68
            $this->updateTuple($tuple, $task, $status, $cascade, $node, $state);
69
            if ($persist) {
70
                $this->snap($tuple, true);
71 2918
            }
72 2918
            return $tuple;
73 650
        }
74 650
75
        $tuple = new Tuple($task, $entity, $cascade, $status ?? Tuple::STATUS_PREPARING);
76
        if ($node !== null) {
77 650
            $tuple->node = $node;
78
        }
79
        if ($state !== null) {
80 2918
            $tuple->state = $state;
81 2918
        }
82
83
        return $this->smartAttachTuple($tuple, $highPriority, $persist);
84 2918
    }
85
86
    public function attachStore(
87
        object $entity,
88 2918
        bool $cascade,
89
        ?Node $node = null,
90
        ?State $state = null,
91 2918
        bool $highPriority = false,
92
        bool $persist = false,
93 2918
    ): Tuple {
94
        return $this->attach($entity, Tuple::TASK_STORE, $cascade, $node, $state, null, $highPriority, $persist);
95
    }
96
97 2918
    public function attachDelete(
98
        object $entity,
99
        bool $cascade,
100 2918
        ?Node $node = null,
101
        ?State $state = null,
102 2918
    ): Tuple {
103 1884
        return $this->attach($entity, Tuple::TASK_DELETE, $cascade, $node, $state);
104
    }
105
106 2918
    public function offsetGet(object $entity): ?Tuple
107 496
    {
108
        return $this->all->contains($entity) ? $this->all->getTuple($entity) : null;
109 2918
    }
110
111
    /**
112 2918
     * Smart iterator
113
     *
114 2918
     * @return \Traversable<object, Tuple>
115
     */
116
    public function openIterator(): \Traversable
117 2812
    {
118
        if ($this->iterating) {
119
            throw new \RuntimeException('Iterator is already open.');
120
        }
121
        $this->iterating = true;
122
        $this->activatePriorityStorage();
123
        $this->unprocessed = [];
124
125 2812
        // Snap all entities before store
126
        /** @var object $entity */
127
        foreach ($this->storage as $entity => $tuple) {
128 496
            $this->snap($tuple);
129
            if (!isset($tuple->node)) {
130
                $this->storage->detach($entity);
131
            }
132
        }
133
134 496
        $stage = 0;
135
        do {
136
            // High priority first
137 2918
            if ($this->priorityStorage->count() > 0) {
138
                $priorityStorage = $this->priorityStorage;
139 2918
                foreach ($priorityStorage as $entity => $tuple) {
140
                    yield $entity => $tuple;
141
                    $this->trashIt($entity, $tuple, $priorityStorage);
142
                }
143
                continue;
144
            }
145
            // Other
146
            if ($this->storage->count() === 0) {
147 2916
                break;
148
            }
149 2916
            $pool = $this->storage;
150
            if ($stage === 0) {
151
                foreach ($this->storage as $entity => $tuple) {
152 2916
                    if ($tuple->status !== Tuple::STATUS_PREPARING) {
153 2916
                        continue;
154 2916
                    }
155
156
                    yield $entity => $tuple;
157 2916
                    $this->trashIt($entity, $tuple, $pool);
158
                    // Check priority
159 2900
                    if ($this->priorityStorage->count() > 0) {
160 2900
                        continue 2;
161 2900
                    }
162 80
                }
163
                $this->priorityAutoAttach = true;
164 2900
                $stage = 1;
165
            }
166
            if ($stage === 1) {
167
                /** @var object $entity */
168 2916
                foreach ($this->storage as $entity => $tuple) {
169
                    if ($tuple->status !== Tuple::STATUS_WAITING || $tuple->task === Tuple::TASK_DELETE) {
170
                        continue;
171 2916
                    }
172
173
                    $tuple->status = Tuple::STATUS_WAITED;
174
                    yield $entity => $tuple;
175
                    $this->trashIt($entity, $tuple, $this->storage);
176
                    // Check priority
177
                    if ($this->priorityStorage->count() > 0) {
178
                        continue 2;
179
                    }
180
                }
181 2916
                $stage = 2;
182 2804
            }
183
            if ($stage === 2) {
184 2900
                $this->happens = 0;
185 2900
                /** @var object $entity */
186 2900
                foreach ($this->storage as $entity => $tuple) {
187
                    if ($tuple->task === Tuple::TASK_DELETE) {
188 2900
                        $tuple->task = Tuple::TASK_FORCE_DELETE;
189
                    }
190 2900
                    if ($tuple->status === Tuple::STATUS_WAITING) {
191
                        $tuple->status = Tuple::STATUS_WAITED;
192 2900
                    } elseif ($tuple->status === Tuple::STATUS_DEFERRED) {
193 2900
                        $tuple->status = Tuple::STATUS_PROPOSED;
194 2900
                    }
195 2900
                    yield $entity => $tuple;
196 1808
                    $this->trashIt($entity, $tuple, $this->storage);
197
                    // Check priority
198 2900
                    if ($this->priorityStorage->count() > 0) {
199 2860
                        continue 2;
200
                    }
201 2860
                }
202
                $hasUnresolved = $this->unprocessed !== [];
203
                if ($this->happens !== 0 && $hasUnresolved) {
204
                    /** @psalm-suppress InvalidIterator */
205 2852
                    foreach ($this->unprocessed as $item) {
206 2852
                        $this->storage->attach($item);
207 2852
                    }
208
                    $this->unprocessed = [];
209 2852
                    continue;
210 2852
                }
211
                if ($this->happens === 0 && (\count($pool) > 0 || $hasUnresolved)) {
212 1824
                    throw new PoolException('Pool has gone into an infinite loop.');
213 1824
                }
214 1824
            }
215 1824
        } while (true);
216 957
        $this->closeIterator();
217
    }
218 1364
219 1364
    public function count(): int
220 1348
    {
221
        return \count($this->storage) + ($this->priorityEnabled ? $this->priorityStorage->count() : 0);
222 1348
    }
223
224
    /**
225
     * Get unresolved entity tuples.
226 2852
     *
227 2852
     * @return iterable<Tuple>
228
     */
229 2852
    public function getUnresolved(): iterable
230 2852
    {
231 2852
        if ($this->iterating) {
232
            return $this->unprocessed;
233 957
        }
234 957
        throw new PoolException('The Pool iterator isn\'t open.');
235 957
    }
236 328
237
    /**
238 957
     * @return iterable<object, Tuple> All valid tuples
239 371
     */
240 693
    public function getAllTuples(): iterable
241 605
    {
242
        foreach ($this->all as $entity => $tuple) {
243 957
            if (isset($tuple->node)) {
244 957
                yield $entity => $tuple;
245 949
            }
246
        }
247 949
    }
248
249
    /**
250
     * Close opened iterator
251 2812
     */
252 2812
    public function closeIterator(): void
253
    {
254 24
        $this->iterating = false;
255 24
        $this->priorityEnabled = false;
256
        $this->priorityAutoAttach = false;
257 24
        unset($this->priorityStorage, $this->unprocessed);
258 24
    }
259
260 2812
    private function smartAttachTuple(Tuple $tuple, bool $highPriority = false, bool $snap = false): Tuple
261 24
    {
262
        if ($tuple->status === Tuple::STATUS_PROCESSED) {
263
            $this->all->attach($tuple);
264
            return $tuple;
265 2804
        }
266
        if ($tuple->status === Tuple::STATUS_PREPARING && $this->all->contains($tuple->entity)) {
267
            return $this->all->getTuple($tuple->entity);
268
        }
269
        $this->all->attach($tuple);
270
271
        if ($this->iterating || $snap) {
272
            $this->snap($tuple);
273
        }
274
275
        if (isset($tuple->node) && $tuple->task === Tuple::TASK_DELETE) {
276
            $tuple->state->setStatus(Node::SCHEDULED_DELETE);
277
        }
278 24
        if (($this->priorityAutoAttach || $highPriority) && $tuple->status === Tuple::STATUS_PREPARING) {
279
            $this->priorityStorage->attach($tuple);
280 24
        } else {
281 24
            $this->storage->attach($tuple);
282
        }
283
        return $tuple;
284
    }
285
286
    /**
287
     * Make snapshot: create Node, State if not exists. Also attach Mapper
288
     */
289 2916
    private function snap(Tuple $tuple, bool $forceUpdateState = false): void
290
    {
291 2916
        $entity = $tuple->entity;
292 2900
        /** @var Node|null $node */
293 2900
        $node = $tuple->node ?? $this->orm->getHeap()->get($entity);
294 2900
295
        if (($node === null && $tuple->task !== Tuple::TASK_STORE) || $entity instanceof ReferenceInterface) {
296
            return;
297
        }
298
        $tuple->mapper ??= $this->orm->getMapper($node?->getRole() ?? $entity);
299
        if ($node === null) {
300
            // Create new Node
301
            $node = new Node(Node::NEW, [], $tuple->mapper->getRole());
302 2916
            if (isset($tuple->state)) {
303
                $tuple->state->setData($tuple->mapper->fetchFields($entity));
304 2916
                $node->setState($tuple->state);
305 2916
            }
306 2916
            $this->orm->getHeap()->attach($entity, $node);
307 2916
        }
308
        $tuple->node = $node;
309
        if (!isset($tuple->state)) {
310
            $tuple->state = $tuple->node->createState();
311
            $tuple->state->setData($tuple->mapper->fetchFields($entity));
312
        } elseif ($forceUpdateState) {
313
            $tuple->state->setData($tuple->mapper->fetchFields($entity));
314 2916
        }
315
316 2916
        // Backup State
317
        if (!$this->iterating) {
318 2916
            $tuple->persist = clone $tuple->state;
319
        }
320 2916
    }
321 80
322
    private function trashIt(object $entity, Tuple $tuple, TupleStorage $storage): void
323 2916
    {
324 2916
        if ($tuple->status === Tuple::STATUS_UNPROCESSED) {
325
            $storage->detach($entity);
326 1578
            $tuple->status = Tuple::STATUS_PREPROCESSED;
327 1578
            $this->unprocessed[] = $tuple;
328
            return;
329
        }
330
331 1578
        if ($tuple->status >= Tuple::STATUS_PREPROCESSED) {
332
            $storage->detach($entity);
333 2916
            $tuple->status = Tuple::STATUS_PROCESSED;
334 2916
            ++$this->happens;
335 2916
            return;
336 2916
        }
337
338
        if ($tuple->status % 2 === 0) {
339
            ++$tuple->status;
340 2916
            ++$this->happens;
341 80
        }
342
343
        if ($storage !== $this->storage) {
344
            $storage->detach($entity);
345 2860
            $this->storage->attach($tuple);
346
        }
347 2860
    }
348
349 2860
    private function activatePriorityStorage(): void
350 24
    {
351 24
        if ($this->priorityEnabled === true) {
352 24
            return;
353
        }
354
        $this->priorityEnabled = true;
355 2860
        $this->priorityStorage = new TupleStorage();
356 2828
    }
357 2828
358 2828
    private function updateTuple(Tuple $tuple, int $task, ?int $status, bool $cascade, ?Node $node, ?State $state): void
359
    {
360
        if ($status !== null && $tuple->status !== $status) {
361 1824
            if ($status === Tuple::STATUS_PROCESSED) {
362 1196
                $this->storage->detach($tuple->entity);
363 1196
                return;
364
            }
365 1824
            if ($tuple->status === Tuple::STATUS_PREPARING) {
366
                $tuple->status = $status;
367
            }
368 2916
        }
369
        if ($tuple->task !== $task) {
370 2916
            if ($tuple->task === Tuple::TASK_DELETE) {
371
                $tuple->task = $task;
372
            } elseif ($task === Tuple::TASK_FORCE_DELETE) {
373 2916
                $tuple->task = $task;
374 2916
            }
375
        }
376
377 650
        $tuple->cascade = $tuple->cascade || $cascade;
378
        $node === null or $tuple->node = $tuple->node ?? $node;
379 650
        $state === null or $tuple->state = $tuple->state ?? $state;
380
    }
381
}
382