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 10
CRAP Score 13.8217

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
ccs 10
cts 14
cp 0.7143
crap 13.8217
rs 7.3166

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
                    } 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