Completed
Push — master ( 56408e...5dfd0e )
by Maciej
22:59 queued 12:13
created

CachingIterator::getIterator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2.0625
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Iterator;
6
7
use Generator;
8
use RuntimeException;
9
use Traversable;
10
use function current;
11
use function key;
12
use function next;
13
use function reset;
14
15
/**
16
 * Iterator for wrapping a Traversable and caching its results.
17
 *
18
 * By caching results, this iterators allows a Traversable to be counted and
19
 * rewound multiple times, even if the wrapped object does not natively support
20
 * those operations (e.g. MongoDB\Driver\Cursor).
21
 *
22
 * @internal
23
 */
24
final class CachingIterator implements Iterator
25
{
26
    /** @var array */
27
    private $items = [];
28
29
    /** @var Generator|null */
30
    private $iterator;
31
32
    /** @var bool */
33
    private $iteratorAdvanced = false;
34
35
    /** @var bool */
36
    private $iteratorExhausted = false;
37
38
    /**
39
     * Initialize the iterator and stores the first item in the cache. This
40
     * effectively rewinds the Traversable and the wrapping Generator, which
41
     * will execute up to its first yield statement. Additionally, this mimics
42
     * behavior of the SPL iterators and allows users to omit an explicit call
43
     * to rewind() before using the other methods.
44
     */
45 145
    public function __construct(Traversable $iterator)
46
    {
47 145
        $this->iterator = $this->wrapTraversable($iterator);
48 145
        $this->storeCurrentItem();
49 145
    }
50
51 107
    public function __destruct()
52
    {
53 107
        $this->iterator = null;
54 107
    }
55
56 82
    public function toArray() : array
57
    {
58 82
        $this->exhaustIterator();
59
60 82
        return $this->items;
61
    }
62
63
    /**
64
     * @see http://php.net/iterator.current
65
     *
66
     * @return mixed
67
     */
68 91
    public function current()
69
    {
70 91
        return current($this->items);
71
    }
72
73
    /**
74
     * @see http://php.net/iterator.mixed
75
     *
76
     * @return mixed
77
     */
78 34
    public function key()
79
    {
80 34
        return key($this->items);
81
    }
82
83
    /**
84
     * @see http://php.net/iterator.next
85
     */
86 85
    public function next() : void
87
    {
88 85
        if (! $this->iteratorExhausted) {
89 85
            $this->getIterator()->next();
90 85
            $this->storeCurrentItem();
91
        }
92
93 85
        next($this->items);
94 85
    }
95
96
    /**
97
     * @see http://php.net/iterator.rewind
98
     */
99 30
    public function rewind() : void
100
    {
101
        /* If the iterator has advanced, exhaust it now so that future iteration
102
         * can rely on the cache.
103
         */
104 30
        if ($this->iteratorAdvanced) {
105 20
            $this->exhaustIterator();
106
        }
107
108 30
        reset($this->items);
109 30
    }
110
111
    /**
112
     * @see http://php.net/iterator.valid
113
     */
114 34
    public function valid() : bool
115
    {
116 34
        return $this->key() !== null;
117
    }
118
119
    /**
120
     * Ensures that the inner iterator is fully consumed and cached.
121
     */
122 86
    private function exhaustIterator() : void
123
    {
124 86
        while (! $this->iteratorExhausted) {
125 75
            $this->next();
126
        }
127
128 86
        $this->iterator = null;
129 86
    }
130
131 145
    private function getIterator() : Generator
132
    {
133 145
        if ($this->iterator === null) {
134
            throw new RuntimeException('Iterator has already been destroyed');
135
        }
136
137 145
        return $this->iterator;
138
    }
139
140
    /**
141
     * Stores the current item in the cache.
142
     */
143 145
    private function storeCurrentItem() : void
144
    {
145 145
        $key = $this->getIterator()->key();
146
147 145
        if ($key === null) {
148 96
            return;
149
        }
150
151 135
        $this->items[$key] = $this->getIterator()->current();
152 135
    }
153
154 145
    private function wrapTraversable(Traversable $traversable) : Generator
155
    {
156 145
        foreach ($traversable as $key => $value) {
157 135
            yield $key => $value;
158 85
            $this->iteratorAdvanced = true;
159
        }
160
161 96
        $this->iteratorExhausted = true;
162 96
    }
163
}
164