Completed
Push — master ( 023cf7...b3f7d4 )
by Andreas
02:28
created

AbstractCursor::valid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
nc 1
cc 1
eloc 2
nop 0
crap 1
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
use MongoDB\Collection;
18
use MongoDB\Driver\Cursor;
19
use MongoDB\Driver\ReadPreference;
20
21
/**
22
 * @internal
23
 */
24
abstract class AbstractCursor
25
{
26
    /**
27
     * @var int
28
     */
29
    protected $batchSize;
30
31
    /**
32
     * @var Collection
33
     */
34
    protected $collection;
35
36
    /**
37
     * @var \MongoClient
38
     */
39
    protected $connection;
40
41
    /**
42
     * @var Cursor
43
     */
44
    protected $cursor;
45
46
    /**
47
     * @var \MongoDB\Database
48
     */
49
    protected $db;
50
51
    /**
52
     * @var \IteratorIterator
53
     */
54
    protected $iterator;
55
56
    /**
57
     * @var string
58
     */
59
    protected $ns;
60
61
    /**
62
     * @var array
63
     */
64
    protected $optionNames = [
65
        'batchSize',
66
        'readPreference',
67
    ];
68
69
    /**
70
     * @var array
71
     */
72
    protected $readPreference = [];
73
74
    /**
75
     * @return Cursor
76
     */
77
    abstract protected function ensureCursor();
78
79
    /**
80
     * @return array
81
     */
82
    abstract protected function getCursorInfo();
83
84
    /**
85
     * Create a new cursor
86
     * @link http://www.php.net/manual/en/mongocursor.construct.php
87
     * @param \MongoClient $connection Database connection.
88
     * @param string $ns Full name of database and collection.
89
     */
90 25
    public function __construct(\MongoClient $connection, $ns)
91
    {
92 25
        $this->connection = $connection;
93 25
        $this->ns = $ns;
94
95 25
        $nsParts = explode('.', $ns);
96 25
        $dbName = array_shift($nsParts);
97 25
        $collectionName = implode('.', $nsParts);
98
99 25
        $this->db = $connection->selectDB($dbName)->getDb();
100
101 25
        if ($collectionName) {
102 22
            $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection();
103 22
        }
104 25
    }
105
106
    /**
107
     * Returns the current element
108
     * @link http://www.php.net/manual/en/mongocursor.current.php
109
     * @return array
110
     */
111 6
    public function current()
112
    {
113 6
        $document = $this->ensureIterator()->current();
114 6
        if ($document !== null) {
115 6
            $document = TypeConverter::convertObjectToLegacyArray($document);
116 6
        }
117
118 6
        return $document;
119
    }
120
121
    /**
122
     * Returns the current result's _id
123
     * @link http://www.php.net/manual/en/mongocursor.key.php
124
     * @return string The current result's _id as a string.
125
     */
126 6
    public function key()
127
    {
128 6
        return $this->ensureIterator()->key();
129
    }
130
131
    /**
132
     * Advances the cursor to the next result
133
     * @link http://www.php.net/manual/en/mongocursor.next.php
134
     * @throws \MongoConnectionException
135
     * @throws \MongoCursorTimeoutException
136
     * @return void
137
     */
138 6
    public function next()
139
    {
140 6
        $this->ensureIterator()->next();
141 6
    }
142
143
    /**
144
     * Returns the cursor to the beginning of the result set
145
     * @throws \MongoConnectionException
146
     * @throws \MongoCursorTimeoutException
147
     * @return void
148
     */
149 23
    public function rewind()
150
    {
151
        // We can recreate the cursor to allow it to be rewound
152 23
        $this->reset();
153 23
        $this->ensureIterator()->rewind();
154 22
    }
155
156
    /**
157
     * Checks if the cursor is reading a valid result.
158
     * @link http://www.php.net/manual/en/mongocursor.valid.php
159
     * @return boolean If the current result is not null.
160
     */
161 22
    public function valid()
162
    {
163 22
        return $this->ensureIterator()->valid();
164
    }
165
166
    /**
167
     * Limits the number of elements returned in one batch.
168
     *
169
     * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
170
     * @param int $batchSize The number of results to return per batch
171
     * @return $this Returns this cursor.
172
     */
173 1
    public function batchSize($batchSize)
174
    {
175 1
        $this->batchSize = $batchSize;
176
177 1
        return $this;
178
    }
179
180
    /**
181
     * Checks if there are documents that have not been sent yet from the database for this cursor
182
     * @link http://www.php.net/manual/en/mongocursor.dead.php
183
     * @return boolean Returns if there are more results that have not been sent to the client, yet.
184
     */
185
    public function dead()
186
    {
187
        return $this->ensureCursor()->isDead();
188
    }
189
190
    /**
191
     * Get the read preference for this query
192
     * @link http://www.php.net/manual/en/mongocursor.getreadpreference.php
193
     * @return array
194
     */
195
    public function getReadPreference()
196
    {
197
        return $this->readPreference;
198
    }
199
200
    /**
201
     * @return array
202
     */
203 2
    public function info()
204
    {
205 2
        return $this->getCursorInfo() + $this->getIterationInfo();
206
    }
207
208
    /**
209
     * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
210
     * @param string $readPreference
211
     * @param array $tags
212
     * @return $this Returns this cursor.
213
     */
214 2
    public function setReadPreference($readPreference, $tags = null)
215
    {
216
        $availableReadPreferences = [
217 2
            \MongoClient::RP_PRIMARY,
218 2
            \MongoClient::RP_PRIMARY_PREFERRED,
219 2
            \MongoClient::RP_SECONDARY,
220 2
            \MongoClient::RP_SECONDARY_PREFERRED,
221
            \MongoClient::RP_NEAREST
222 2
        ];
223 2
        if (! in_array($readPreference, $availableReadPreferences)) {
224
            trigger_error("The value '$readPreference' is not valid as read preference type", E_WARNING);
225
            return $this;
226
        }
227
228 2
        if ($readPreference == \MongoClient::RP_PRIMARY && count($tags)) {
229
            trigger_error("You can't use read preference tags with a read preference of PRIMARY", E_WARNING);
230
            return $this;
231
        }
232
233 2
        $this->readPreference = [
234 2
            'type' => $readPreference,
235
            'tagsets' => $tags
236 2
        ];
237
238 2
        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 24
    protected function getOptions($optionNames = null)
259
    {
260 24
        $options = [];
261
262 24
        if ($optionNames === null) {
263 23
            $optionNames = $this->optionNames;
264 23
        }
265
266 24
        foreach ($optionNames as $option) {
267 24
            $converter = 'convert' . ucfirst($option);
268 24
            $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
269
270 24
            if ($value === null) {
271 24
                continue;
272
            }
273
274 19
            $options[$option] = $value;
275 24
        }
276
277 24
        return $options;
278
    }
279
280
    /**
281
     * @return ReadPreference|null
282
     */
283 23
    protected function convertReadPreference()
284
    {
285 23
        $type = array_key_exists('type', $this->readPreference) ? $this->readPreference['type'] : null;
286 23
        if ($type === null) {
287 21
            return null;
288
        }
289
290
        switch ($type) {
291 2
            case \MongoClient::RP_PRIMARY_PREFERRED:
292
                $mode = ReadPreference::RP_PRIMARY_PREFERRED;
293
                break;
294 2
            case \MongoClient::RP_SECONDARY:
295 1
                $mode = ReadPreference::RP_SECONDARY;
296 1
                break;
297 1
            case \MongoClient::RP_SECONDARY_PREFERRED:
298
                $mode = ReadPreference::RP_SECONDARY_PREFERRED;
299
                break;
300 1
            case \MongoClient::RP_NEAREST:
301
                $mode = ReadPreference::RP_NEAREST;
302
                break;
303 1
            default:
304 1
                $mode = ReadPreference::RP_PRIMARY;
305 1
        }
306
307 2
        $tagSets = array_key_exists('tagsets', $this->readPreference) ? $this->readPreference['tagsets'] : [];
308
309 2
        return new ReadPreference($mode, $tagSets);
310
    }
311
312
    /**
313
     * @return \IteratorIterator
314
     */
315 23
    protected function ensureIterator()
316
    {
317 23
        if ($this->iterator === null) {
318 23
            $this->iterator = new \IteratorIterator($this->ensureCursor());
319 22
        }
320
321 22
        return $this->iterator;
322
    }
323
324
    /**
325
     * @throws \MongoCursorException
326
     */
327 14
    protected function errorIfOpened()
328
    {
329 14
        if ($this->cursor === null) {
330 14
            return;
331
        }
332
333
        throw new \MongoCursorException('cannot modify cursor after beginning iteration.');
334
    }
335
336
    /**
337
     * @return array
338
     */
339 2
    protected function getIterationInfo()
340
    {
341
        $iterationInfo = [
342 2
            'started_iterating' => $this->cursor !== null,
343 2
        ];
344
345 2
        if ($this->cursor !== null) {
346 2
            switch ($this->cursor->getServer()->getType()) {
347 2
                case \MongoDB\Driver\Server::TYPE_RS_ARBITER:
348
                    $typeString = 'ARBITER';
349
                    break;
350 2
                case \MongoDB\Driver\Server::TYPE_MONGOS:
351
                    $typeString = 'MONGOS';
352
                    break;
353 2
                case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
354
                    $typeString = 'PRIMARY';
355
                    break;
356 2
                case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
357
                    $typeString = 'SECONDARY';
358
                    break;
359 2
                default:
360 2
                    $typeString = 'STANDALONE';
361 2
            }
362
363
            $iterationInfo += [
364 2
                'id' => (string) $this->cursor->getId(),
365 2
                'at' => null, // @todo Complete info for cursor that is iterating
366 2
                'numReturned' => null, // @todo Complete info for cursor that is iterating
367 2
                'server' => null, // @todo Complete info for cursor that is iterating
368 2
                'host' => $this->cursor->getServer()->getHost(),
369 2
                'port' => $this->cursor->getServer()->getPort(),
370 2
                'connection_type_desc' => $typeString,
371
            ];
372 2
        }
373
374 2
        return $iterationInfo;
375
    }
376
377
    /**
378
     * @throws \Exception
379
     */
380
    protected function notImplemented()
381
    {
382
        throw new \Exception('Not implemented');
383
    }
384
385
    /**
386
     * Clears the cursor
387
     *
388
     * This is generic but implemented as protected since it's only exposed in MongoCursor
389
     */
390 23
    protected function reset()
391
    {
392 23
        $this->cursor = null;
393 23
        $this->iterator = null;
394 23
    }
395
}
396