Completed
Push — master ( 890752...662a8c )
by Andreas
02:51
created

AbstractCursor   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 82.24%

Importance

Changes 8
Bugs 3 Features 2
Metric Value
wmc 36
c 8
b 3
f 2
lcom 1
cbo 6
dl 0
loc 354
ccs 88
cts 107
cp 0.8224
rs 8.8

20 Methods

Rating   Name   Duplication   Size   Complexity  
ensureCursor() 0 1 ?
getCursorInfo() 0 1 ?
A key() 0 8 2
A rewind() 0 8 1
A valid() 0 8 2
A batchSize() 0 6 1
A dead() 0 4 1
A info() 0 4 1
A setReadPreference() 0 6 1
A timeout() 0 4 1
A errorIfOpened() 0 8 2
A notImplemented() 0 4 1
A reset() 0 6 1
A __construct() 0 15 2
A current() 0 13 3
A next() 0 12 2
B getOptions() 0 21 5
A ensureIterator() 0 10 2
A wrapTraversable() 0 6 2
B getIterationInfo() 0 38 6
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 */
15
16
namespace Alcaeus\MongoDbAdapter;
17
18
use Alcaeus\MongoDbAdapter\Helper\ReadPreference;
19
use MongoDB\Collection;
20
use MongoDB\Driver\Cursor;
21
22
/**
23
 * @internal
24
 */
25
abstract class AbstractCursor
26
{
27
    use ReadPreference;
28
29
    /**
30
     * @var int
31
     */
32
    protected $batchSize = 0;
33
34
    /**
35
     * @var Collection
36
     */
37
    protected $collection;
38
39
    /**
40
     * @var \MongoClient
41
     */
42
    protected $connection;
43
44
    /**
45
     * @var Cursor
46
     */
47
    protected $cursor;
48
49
    /**
50
     * @var \MongoDB\Database
51
     */
52
    protected $db;
53
54
    /**
55
     * @var \Iterator
56
     */
57
    protected $iterator;
58
59
    /**
60
     * @var string
61
     */
62
    protected $ns;
63
64
    /**
65
     * @var bool
66
     */
67
    protected $startedIterating = false;
68
69
    /**
70
     * @var int
71
     */
72
    protected $position = 0;
73
74
    /**
75
     * @var array
76
     */
77
    protected $optionNames = [
78
        'batchSize',
79
        'readPreference',
80
    ];
81
82
    /**
83
     * @return Cursor
84
     */
85
    abstract protected function ensureCursor();
86
87
    /**
88
     * @return array
89
     */
90
    abstract protected function getCursorInfo();
91
92
    /**
93
     * Create a new cursor
94
     * @link http://www.php.net/manual/en/mongocursor.construct.php
95
     * @param \MongoClient $connection Database connection.
96
     * @param string $ns Full name of database and collection.
97
     */
98 42
    public function __construct(\MongoClient $connection, $ns)
99
    {
100 42
        $this->connection = $connection;
101 42
        $this->ns = $ns;
102
103 42
        $nsParts = explode('.', $ns);
104 42
        $dbName = array_shift($nsParts);
105 42
        $collectionName = implode('.', $nsParts);
106
107 42
        $this->db = $connection->selectDB($dbName)->getDb();
108
109 42
        if ($collectionName) {
110 34
            $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection();
111
        }
112 42
    }
113
114
    /**
115
     * Returns the current element
116
     * @link http://www.php.net/manual/en/mongocursor.current.php
117
     * @return array
118
     */
119 19
    public function current()
120
    {
121 19
        if (! $this->startedIterating) {
122 2
            return null;
123
        }
124
125 19
        $document = $this->ensureIterator()->current();
126 19
        if ($document !== null) {
127 19
            $document = TypeConverter::toLegacy($document);
128
        }
129
130 19
        return $document;
131
    }
132
133
    /**
134
     * Returns the current result's _id
135
     * @link http://www.php.net/manual/en/mongocursor.key.php
136
     * @return string The current result's _id as a string.
137
     */
138 11
    public function key()
139
    {
140 11
        if (! $this->startedIterating) {
141 2
            return null;
142
        }
143
144 10
        return $this->ensureIterator()->key();
145
    }
146
147
    /**
148
     * Advances the cursor to the next result, and returns that result
149
     * @link http://www.php.net/manual/en/mongocursor.next.php
150
     * @throws \MongoConnectionException
151
     * @throws \MongoCursorTimeoutException
152
     * @return array Returns the next object
153
     */
154 19
    public function next()
155
    {
156 19
        if (! $this->startedIterating) {
157 2
            $this->ensureIterator();
158 2
            $this->startedIterating = true;
159
        } else {
160 19
            $this->ensureIterator()->next();
161 19
            $this->position++;
162
        }
163
164 19
        return $this->current();
165
    }
166
167
    /**
168
     * Returns the cursor to the beginning of the result set
169
     * @throws \MongoConnectionException
170
     * @throws \MongoCursorTimeoutException
171
     * @return void
172
     */
173 36
    public function rewind()
174
    {
175
        // We can recreate the cursor to allow it to be rewound
176 36
        $this->reset();
177 36
        $this->startedIterating = true;
178 36
        $this->position = 0;
179 36
        $this->ensureIterator()->rewind();
180 34
    }
181
182
    /**
183
     * Checks if the cursor is reading a valid result.
184
     * @link http://www.php.net/manual/en/mongocursor.valid.php
185
     * @return boolean If the current result is not null.
186
     */
187 34
    public function valid()
188
    {
189 34
        if (! $this->startedIterating) {
190 1
            return false;
191
        }
192
193 34
        return $this->ensureIterator()->valid();
194
    }
195
196
    /**
197
     * Limits the number of elements returned in one batch.
198
     *
199
     * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
200
     * @param int $batchSize The number of results to return per batch
201
     * @return $this Returns this cursor.
202
     */
203 1
    public function batchSize($batchSize)
204
    {
205 1
        $this->batchSize = $batchSize;
206
207 1
        return $this;
208
    }
209
210
    /**
211
     * Checks if there are documents that have not been sent yet from the database for this cursor
212
     * @link http://www.php.net/manual/en/mongocursor.dead.php
213
     * @return boolean Returns if there are more results that have not been sent to the client, yet.
214
     */
215
    public function dead()
216
    {
217
        return $this->ensureCursor()->isDead();
218
    }
219
220
    /**
221
     * @return array
222
     */
223 3
    public function info()
224
    {
225 3
        return $this->getCursorInfo() + $this->getIterationInfo();
226
    }
227
228
    /**
229
     * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
230
     * @param string $readPreference
231
     * @param array $tags
232
     * @return $this Returns this cursor.
233
     */
234 42
    public function setReadPreference($readPreference, $tags = null)
235
    {
236 42
        $this->setReadPreferenceFromParameters($readPreference, $tags);
237
238 42
        return $this;
239
    }
240
241
    /**
242
     * Sets a client-side timeout for this query
243
     * @link http://www.php.net/manual/en/mongocursor.timeout.php
244
     * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
245
     * @return $this Returns this cursor
246
     */
247
    public function timeout($ms)
0 ignored issues
show
Unused Code introduced by
The parameter $ms is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
248
    {
249
        $this->notImplemented();
250
    }
251
252
    /**
253
     * Applies all options set on the cursor, overwriting any options that have already been set
254
     *
255
     * @param array $optionNames Array of option names to be applied (will be read from properties)
256
     * @return array
257
     */
258 39
    protected function getOptions($optionNames = null)
259
    {
260 39
        $options = [];
261
262 39
        if ($optionNames === null) {
263 37
            $optionNames = $this->optionNames;
264
        }
265
266 39
        foreach ($optionNames as $option) {
267 39
            $converter = 'convert' . ucfirst($option);
268 39
            $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
269
270 39
            if ($value === null) {
271 29
                continue;
272
            }
273
274 38
            $options[$option] = $value;
275
        }
276
277 39
        return $options;
278
    }
279
280
    /**
281
     * @return \Iterator
282
     */
283 37
    protected function ensureIterator()
284
    {
285 37
        if ($this->iterator === null) {
286
            // MongoDB\Driver\Cursor needs to be wrapped into a \Generator so that a valid \Iterator with working implementations of
287
            // next, current, valid, key and rewind is returned. These methods don't work if we wrap the Cursor inside an \IteratorIterator
288 37
            $this->iterator = $this->wrapTraversable($this->ensureCursor());
289
        }
290
291 35
        return $this->iterator;
292
    }
293
294
    /**
295
     * @param \Traversable $traversable
296
     * @return \Generator
297
     */
298 8
    protected function wrapTraversable(\Traversable $traversable)
299
    {
300 8
        foreach ($traversable as $key => $value) {
301 8
            yield $key => $value;
302
        }
303 8
    }
304
305
    /**
306
     * @throws \MongoCursorException
307
     */
308 18
    protected function errorIfOpened()
309
    {
310 18
        if ($this->cursor === null) {
311 18
            return;
312
        }
313
314
        throw new \MongoCursorException('cannot modify cursor after beginning iteration.');
315
    }
316
317
    /**
318
     * @return array
319
     */
320 3
    protected function getIterationInfo()
321
    {
322
        $iterationInfo = [
323 3
            'started_iterating' => $this->cursor !== null,
324
        ];
325
326 3
        if ($this->cursor !== null) {
327 3
            switch ($this->cursor->getServer()->getType()) {
328
                case \MongoDB\Driver\Server::TYPE_RS_ARBITER:
329
                    $typeString = 'ARBITER';
330
                    break;
331
                case \MongoDB\Driver\Server::TYPE_MONGOS:
332
                    $typeString = 'MONGOS';
333
                    break;
334
                case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
335
                    $typeString = 'PRIMARY';
336
                    break;
337 3
                case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
338
                    $typeString = 'SECONDARY';
339
                    break;
340
                default:
341 3
                    $typeString = 'STANDALONE';
342
            }
343
344 3
            $cursorId = (string) $this->cursor->getId();
345
            $iterationInfo += [
346 3
                'id' => (int) $cursorId,
347 3
                'at' => $this->position,
348 3
                'numReturned' => $this->position, // This can't be obtained from the new cursor
349 3
                'server' => sprintf('%s:%d;-;.;%d', $this->cursor->getServer()->getHost(), $this->cursor->getServer()->getPort(), getmypid()),
350 3
                'host' => $this->cursor->getServer()->getHost(),
351 3
                'port' => $this->cursor->getServer()->getPort(),
352 3
                'connection_type_desc' => $typeString,
353
            ];
354
        }
355
356 3
        return $iterationInfo;
357
    }
358
359
    /**
360
     * @throws \Exception
361
     */
362
    protected function notImplemented()
363
    {
364
        throw new \Exception('Not implemented');
365
    }
366
367
    /**
368
     * Clears the cursor
369
     *
370
     * This is generic but implemented as protected since it's only exposed in MongoCursor
371
     */
372 37
    protected function reset()
373
    {
374 37
        $this->startedIterating = false;
375 37
        $this->cursor = null;
376 37
        $this->iterator = null;
377 37
    }
378
}
379