Passed
Push — 2.x ( b81303...ac0a18 )
by Aleksei
16:00
created

Pool::attach()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 30
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.3073

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 12
nc 6
nop 8
dl 0
loc 30
ccs 10
cts 13
cp 0.7692
crap 5.3073
rs 9.5555
c 1
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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