Pool::openIterator()   F
last analyzed

Complexity

Conditions 30
Paths 1207

Size

Total Lines 104
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 32.9235

Importance

Changes 0
Metric Value
cc 30
eloc 65
c 0
b 0
f 0
nc 1207
nop 0
dl 0
loc 104
ccs 46
cts 54
cp 0.8519
crap 32.9235
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
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
                    } elseif ($tuple->status === Tuple::STATUS_DEFERRED_RESOLVED) {
195 2900
                        $tuple->status = Tuple::STATUS_PROPOSED_RESOLVED;
196 1808
                    }
197
                    yield $entity => $tuple;
198 2900
                    $this->trashIt($entity, $tuple, $this->storage);
199 2860
                    // Check priority
200
                    if ($this->priorityStorage->count() > 0) {
201 2860
                        continue 2;
202
                    }
203
                }
204
                $hasUnresolved = $this->unprocessed !== [];
205 2852
                if ($this->happens !== 0 && $hasUnresolved) {
206 2852
                    /** @psalm-suppress InvalidIterator */
207 2852
                    foreach ($this->unprocessed as $item) {
208
                        $this->storage->attach($item);
209 2852
                    }
210 2852
                    $this->unprocessed = [];
211
                    continue;
212 1824
                }
213 1824
214 1824
                if ($this->happens === 0 && (\count($pool) > 0 || $hasUnresolved)) {
215 1824
                    throw new PoolException('Pool has gone into an infinite loop.');
216 957
                }
217
            }
218 1364
        } while (true);
219 1364
        $this->closeIterator();
220 1348
    }
221
222 1348
    public function count(): int
223
    {
224
        return \count($this->storage) + ($this->priorityEnabled ? $this->priorityStorage->count() : 0);
225
    }
226 2852
227 2852
    /**
228
     * Get unresolved entity tuples.
229 2852
     *
230 2852
     * @return iterable<Tuple>
231 2852
     */
232
    public function getUnresolved(): iterable
233 957
    {
234 957
        if ($this->iterating) {
235 957
            return $this->unprocessed;
236 328
        }
237
        throw new PoolException('The Pool iterator isn\'t open.');
238 957
    }
239 371
240 693
    /**
241 605
     * @return iterable<object, Tuple> All valid tuples
242
     */
243 957
    public function getAllTuples(): iterable
244 957
    {
245 949
        foreach ($this->all as $entity => $tuple) {
246
            if (isset($tuple->node)) {
247 949
                yield $entity => $tuple;
248
            }
249
        }
250
    }
251 2812
252 2812
    /**
253
     * Close opened iterator
254 24
     */
255 24
    public function closeIterator(): void
256
    {
257 24
        $this->iterating = false;
258 24
        $this->priorityEnabled = false;
259
        $this->priorityAutoAttach = false;
260 2812
        unset($this->priorityStorage, $this->unprocessed);
261 24
    }
262
263
    private function smartAttachTuple(Tuple $tuple, bool $highPriority = false, bool $snap = false): Tuple
264
    {
265 2804
        if ($tuple->status === Tuple::STATUS_PROCESSED) {
266
            $this->all->attach($tuple);
267
            return $tuple;
268
        }
269
        if ($tuple->status === Tuple::STATUS_PREPARING && $this->all->contains($tuple->entity)) {
270
            return $this->all->getTuple($tuple->entity);
271
        }
272
        $this->all->attach($tuple);
273
274
        if ($this->iterating || $snap) {
275
            $this->snap($tuple);
276
        }
277
278 24
        if (isset($tuple->node) && $tuple->task === Tuple::TASK_DELETE) {
279
            $tuple->state->setStatus(Node::SCHEDULED_DELETE);
280 24
        }
281 24
        if (($this->priorityAutoAttach || $highPriority) && $tuple->status === Tuple::STATUS_PREPARING) {
282
            $this->priorityStorage->attach($tuple);
283
        } else {
284
            $this->storage->attach($tuple);
285
        }
286
        return $tuple;
287
    }
288
289 2916
    /**
290
     * Make snapshot: create Node, State if not exists. Also attach Mapper
291 2916
     */
292 2900
    private function snap(Tuple $tuple, bool $forceUpdateState = false): void
293 2900
    {
294 2900
        $entity = $tuple->entity;
295
        /** @var Node|null $node */
296
        $node = $tuple->node ?? $this->orm->getHeap()->get($entity);
297
298
        if (($node === null && $tuple->task !== Tuple::TASK_STORE) || $entity instanceof ReferenceInterface) {
299
            return;
300
        }
301
        $tuple->mapper ??= $this->orm->getMapper($node?->getRole() ?? $entity);
302 2916
        if ($node === null) {
303
            // Create new Node
304 2916
            $node = new Node(Node::NEW, [], $tuple->mapper->getRole());
305 2916
            if (isset($tuple->state)) {
306 2916
                $tuple->state->setData($tuple->mapper->fetchFields($entity));
307 2916
                $node->setState($tuple->state);
308
            }
309
            $this->orm->getHeap()->attach($entity, $node);
310
        }
311
        $tuple->node = $node;
312
        if (!isset($tuple->state)) {
313
            $tuple->state = $tuple->node->createState();
314 2916
            $tuple->state->setData($tuple->mapper->fetchFields($entity));
315
        } elseif ($forceUpdateState) {
316 2916
            $tuple->state->setData($tuple->mapper->fetchFields($entity));
317
        }
318 2916
319
        // Backup State
320 2916
        if (!$this->iterating) {
321 80
            $tuple->persist = clone $tuple->state;
322
        }
323 2916
    }
324 2916
325
    private function trashIt(object $entity, Tuple $tuple, TupleStorage $storage): void
326 1578
    {
327 1578
        if ($tuple->status === Tuple::STATUS_UNPROCESSED) {
328
            $storage->detach($entity);
329
            $tuple->status = Tuple::STATUS_PREPROCESSED;
330
            $this->unprocessed[] = $tuple;
331 1578
            return;
332
        }
333 2916
334 2916
        if ($tuple->status >= Tuple::STATUS_PREPROCESSED) {
335 2916
            $storage->detach($entity);
336 2916
            $tuple->status = Tuple::STATUS_PROCESSED;
337
            ++$this->happens;
338
            return;
339
        }
340 2916
341 80
        if ($tuple->status % 2 === 0) {
342
            ++$tuple->status;
343
            ++$this->happens;
344
        }
345 2860
346
        if ($storage !== $this->storage) {
347 2860
            $storage->detach($entity);
348
            $this->storage->attach($tuple);
349 2860
        }
350 24
    }
351 24
352 24
    private function activatePriorityStorage(): void
353
    {
354
        if ($this->priorityEnabled === true) {
355 2860
            return;
356 2828
        }
357 2828
        $this->priorityEnabled = true;
358 2828
        $this->priorityStorage = new TupleStorage();
359
    }
360
361 1824
    private function updateTuple(Tuple $tuple, int $task, ?int $status, bool $cascade, ?Node $node, ?State $state): void
362 1196
    {
363 1196
        if ($status !== null && $tuple->status !== $status) {
364
            if ($status === Tuple::STATUS_PROCESSED) {
365 1824
                $this->storage->detach($tuple->entity);
366
                return;
367
            }
368 2916
            if ($tuple->status === Tuple::STATUS_PREPARING) {
369
                $tuple->status = $status;
370 2916
            }
371
        }
372
        if ($tuple->task !== $task) {
373 2916
            if ($tuple->task === Tuple::TASK_DELETE) {
374 2916
                $tuple->task = $task;
375
            } elseif ($task === Tuple::TASK_FORCE_DELETE) {
376
                $tuple->task = $task;
377 650
            }
378
        }
379 650
380
        $tuple->cascade = $tuple->cascade || $cascade;
381
        $node === null or $tuple->node = $tuple->node ?? $node;
382
        $state === null or $tuple->state = $tuple->state ?? $state;
383
    }
384
}
385