Completed
Push — master ( 728703...b64a14 )
by Andreas
13s
created

AbstractCursor   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 398
Duplicated Lines 2.76 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 86.21%

Importance

Changes 0
Metric Value
dl 11
loc 398
rs 8.6
c 0
b 0
f 0
ccs 100
cts 116
cp 0.8621
wmc 37
lcom 1
cbo 6

22 Methods

Rating   Name   Duplication   Size   Complexity  
ensureCursor() 0 1 ?
getCursorInfo() 0 1 ?
A batchSize() 0 6 1
A dead() 0 4 1
A info() 0 4 1
A setReadPreference() 0 6 1
A errorIfOpened() 0 8 2
A notImplemented() 0 4 1
B getOptions() 0 21 5
A __construct() 0 15 2
A current() 0 4 1
A key() 0 4 1
A next() 11 16 3
A rewind() 0 9 1
A valid() 0 4 1
A timeout() 0 5 1
A ensureIterator() 0 10 2
A wrapTraversable() 0 6 2
B getIterationInfo() 0 38 6
A reset() 0 7 1
A __sleep() 0 4 1
A storeIteratorState() 0 19 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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|null
31
     */
32
    protected $batchSize = null;
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 bool
71
     */
72
    protected $cursorNeedsAdvancing = true;
73
74
    /**
75
     * @var mixed
76
     */
77
    private $current = null;
78
79
    /**
80
     * @var mixed
81
     */
82
    private $key = null;
83
84
    /**
85
     * @var mixed
86
     */
87
    private $valid = false;
88
89
    /**
90
     * @var int
91
     */
92
    protected $position = 0;
93
94
    /**
95
     * @var array
96
     */
97
    protected $optionNames = [
98
        'batchSize',
99
        'readPreference',
100
    ];
101
102
    /**
103
     * @return Cursor
104
     */
105
    abstract protected function ensureCursor();
106
107
    /**
108
     * @return array
109
     */
110
    abstract protected function getCursorInfo();
111
112
    /**
113
     * Create a new cursor
114
     * @link http://www.php.net/manual/en/mongocursor.construct.php
115
     * @param \MongoClient $connection Database connection.
116
     * @param string $ns Full name of database and collection.
117
     */
118 74
    public function __construct(\MongoClient $connection, $ns)
119
    {
120 74
        $this->connection = $connection;
121 74
        $this->ns = $ns;
122
123 74
        $nsParts = explode('.', $ns);
124 74
        $dbName = array_shift($nsParts);
125 74
        $collectionName = implode('.', $nsParts);
126
127 74
        $this->db = $connection->selectDB($dbName)->getDb();
128
129 74
        if ($collectionName) {
130 53
            $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection();
131
        }
132 74
    }
133
134
    /**
135
     * Returns the current element
136
     * @link http://www.php.net/manual/en/mongocursor.current.php
137
     * @return array
138
     */
139 42
    public function current()
140
    {
141 42
        return $this->current;
142
    }
143
144
    /**
145
     * Returns the current result's _id
146
     * @link http://www.php.net/manual/en/mongocursor.key.php
147
     * @return string The current result's _id as a string.
148
     */
149 26
    public function key()
150
    {
151 26
        return $this->key;
152
    }
153
154
    /**
155
     * Advances the cursor to the next result, and returns that result
156
     * @link http://www.php.net/manual/en/mongocursor.next.php
157
     * @throws \MongoConnectionException
158
     * @throws \MongoCursorTimeoutException
159
     * @return array Returns the next object
160
     */
161 44
    public function next()
162
    {
163 44 View Code Duplication
        if (! $this->startedIterating) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164 1
            $this->ensureIterator();
165 1
            $this->startedIterating = true;
166
        } else {
167 44
            if ($this->cursorNeedsAdvancing) {
168 43
                $this->ensureIterator()->next();
169
            }
170
171 44
            $this->cursorNeedsAdvancing = true;
172 44
            $this->position++;
173
        }
174
175 44
        return $this->storeIteratorState();
176
    }
177
178
    /**
179
     * Returns the cursor to the beginning of the result set
180
     * @throws \MongoConnectionException
181
     * @throws \MongoCursorTimeoutException
182
     * @return void
183
     */
184 61
    public function rewind()
185
    {
186
        // We can recreate the cursor to allow it to be rewound
187 61
        $this->reset();
188 61
        $this->startedIterating = true;
189 61
        $this->position = 0;
190 61
        $this->ensureIterator()->rewind();
191 59
        $this->storeIteratorState();
192 59
    }
193
194
    /**
195
     * Checks if the cursor is reading a valid result.
196
     * @link http://www.php.net/manual/en/mongocursor.valid.php
197
     * @return boolean If the current result is not null.
198
     */
199 59
    public function valid()
200
    {
201 59
        return $this->valid;
202
    }
203
204
    /**
205
     * Limits the number of elements returned in one batch.
206
     *
207
     * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
208
     * @param int|null $batchSize The number of results to return per batch
209
     * @return $this Returns this cursor.
210
     */
211 2
    public function batchSize($batchSize)
212
    {
213 2
        $this->batchSize = $batchSize;
214
215 2
        return $this;
216
    }
217
218
    /**
219
     * Checks if there are documents that have not been sent yet from the database for this cursor
220
     * @link http://www.php.net/manual/en/mongocursor.dead.php
221
     * @return boolean Returns if there are more results that have not been sent to the client, yet.
222
     */
223
    public function dead()
224
    {
225
        return $this->ensureCursor()->isDead();
226
    }
227
228
    /**
229
     * @return array
230
     */
231 4
    public function info()
232
    {
233 4
        return $this->getCursorInfo() + $this->getIterationInfo();
234
    }
235
236
    /**
237
     * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
238
     * @param string $readPreference
239
     * @param array $tags
240
     * @return $this Returns this cursor.
241
     */
242 74
    public function setReadPreference($readPreference, $tags = null)
243
    {
244 74
        $this->setReadPreferenceFromParameters($readPreference, $tags);
245
246 74
        return $this;
247
    }
248
249
    /**
250
     * Sets a client-side timeout for this query
251
     * @link http://www.php.net/manual/en/mongocursor.timeout.php
252
     * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
253
     * @return $this Returns this cursor
254
     */
255
    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...
256
    {
257
        trigger_error('The ' . __METHOD__ . ' method is not implemented in mongo-php-adapter', E_USER_WARNING);
258
        return $this;
259
    }
260
261
    /**
262
     * Applies all options set on the cursor, overwriting any options that have already been set
263
     *
264
     * @param array $optionNames Array of option names to be applied (will be read from properties)
265
     * @return array
266
     */
267 68
    protected function getOptions($optionNames = null)
268
    {
269 68
        $options = [];
270
271 68
        if ($optionNames === null) {
272 63
            $optionNames = $this->optionNames;
273
        }
274
275 68
        foreach ($optionNames as $option) {
276 68
            $converter = 'convert' . ucfirst($option);
277 68
            $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
278
279 68
            if ($value === null) {
280 68
                continue;
281
            }
282
283 65
            $options[$option] = $value;
284
        }
285
286 68
        return $options;
287
    }
288
289
    /**
290
     * @return \Iterator
291
     */
292 63
    protected function ensureIterator()
293
    {
294 63
        if ($this->iterator === null) {
295
            // MongoDB\Driver\Cursor needs to be wrapped into a \Generator so that a valid \Iterator with working implementations of
296
            // next, current, valid, key and rewind is returned. These methods don't work if we wrap the Cursor inside an \IteratorIterator
297 63
            $this->iterator = $this->wrapTraversable($this->ensureCursor());
298
        }
299
300 61
        return $this->iterator;
301
    }
302
303
    /**
304
     * @param \Traversable $traversable
305
     * @return \Generator
306
     */
307 21
    protected function wrapTraversable(\Traversable $traversable)
308
    {
309 21
        foreach ($traversable as $key => $value) {
310 21
            yield $key => $value;
311
        }
312 21
    }
313
314
    /**
315
     * @throws \MongoCursorException
316
     */
317 25
    protected function errorIfOpened()
318
    {
319 25
        if ($this->cursor === null) {
320 25
            return;
321
        }
322
323
        throw new \MongoCursorException('cannot modify cursor after beginning iteration.');
324
    }
325
326
    /**
327
     * @return array
328
     */
329 4
    protected function getIterationInfo()
330
    {
331
        $iterationInfo = [
332 4
            'started_iterating' => $this->cursor !== null,
333
        ];
334
335 4
        if ($this->cursor !== null) {
336 4
            switch ($this->cursor->getServer()->getType()) {
337 4
                case \MongoDB\Driver\Server::TYPE_RS_ARBITER:
338
                    $typeString = 'ARBITER';
339
                    break;
340 4
                case \MongoDB\Driver\Server::TYPE_MONGOS:
341
                    $typeString = 'MONGOS';
342
                    break;
343 4
                case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
344
                    $typeString = 'PRIMARY';
345
                    break;
346 4
                case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
347
                    $typeString = 'SECONDARY';
348
                    break;
349
                default:
350 4
                    $typeString = 'STANDALONE';
351
            }
352
353 4
            $cursorId = (string) $this->cursor->getId();
354
            $iterationInfo += [
355 4
                'id' => (int) $cursorId,
356 4
                'at' => $this->position,
357 4
                'numReturned' => $this->position, // This can't be obtained from the new cursor
358 4
                'server' => sprintf('%s:%d;-;.;%d', $this->cursor->getServer()->getHost(), $this->cursor->getServer()->getPort(), getmypid()),
359 4
                'host' => $this->cursor->getServer()->getHost(),
360 4
                'port' => $this->cursor->getServer()->getPort(),
361 4
                'connection_type_desc' => $typeString,
362
            ];
363
        }
364
365 4
        return $iterationInfo;
366
    }
367
368
    /**
369
     * @throws \Exception
370
     */
371
    protected function notImplemented()
372
    {
373
        throw new \Exception('Not implemented');
374
    }
375
376
    /**
377
     * Clears the cursor
378
     *
379
     * This is generic but implemented as protected since it's only exposed in MongoCursor
380
     */
381 62
    protected function reset()
382
    {
383 62
        $this->startedIterating = false;
384 62
        $this->cursor = null;
385 62
        $this->iterator = null;
386 62
        $this->storeIteratorState();
387 62
    }
388
389
    /**
390
     * @return array
391
     */
392 3
    public function __sleep()
393
    {
394 3
        return ['batchSize', 'connection', 'iterator', 'ns', 'optionNames', 'position', 'startedIterating'];
395
    }
396
397
    /**
398
     * Stores the current cursor element.
399
     *
400
     * This is necessary because hasNext() might advance the iterator but we still
401
     * need to be able to return the current object.
402
     */
403 63
    protected function storeIteratorState()
404
    {
405 63
        if (! $this->startedIterating) {
406 62
            $this->current = null;
407 62
            $this->key = null;
408 62
            $this->valid = false;
409 62
            return null;
410
        }
411
412 61
        $this->current = $this->ensureIterator()->current();
413 61
        $this->key = $this->ensureIterator()->key();
414 61
        $this->valid = $this->ensureIterator()->valid();
415
416 61
        if ($this->current !== null) {
417 44
            $this->current = TypeConverter::toLegacy($this->current);
418
        }
419
420 61
        return $this->current;
421
    }
422
}
423