Completed
Pull Request — master (#35)
by
unknown
09:44 queued 13s
created

AbstractCursor::ensureCursor()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 1
ccs 0
cts 0
cp 0
nc 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
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;
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 \IteratorIterator
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 array
71
     */
72
    protected $optionNames = [
73
        'batchSize',
74
        'readPreference',
75
    ];
76
77
    /**
78
     * @return Cursor
79
     */
80
    abstract protected function ensureCursor();
81
82
    /**
83
     * @return array
84
     */
85
    abstract protected function getCursorInfo();
86
87
    /**
88
     * Create a new cursor
89
     * @link http://www.php.net/manual/en/mongocursor.construct.php
90
     * @param \MongoClient $connection Database connection.
91
     * @param string $ns Full name of database and collection.
92
     */
93 42
    public function __construct(\MongoClient $connection, $ns)
94
    {
95 42
        $this->connection = $connection;
96 42
        $this->ns = $ns;
97
98 42
        $nsParts = explode('.', $ns);
99 42
        $dbName = array_shift($nsParts);
100 42
        $collectionName = implode('.', $nsParts);
101
102 42
        $this->db = $connection->selectDB($dbName)->getDb();
103
104 42
        if ($collectionName) {
105 34
            $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection();
106 34
        }
107 42
    }
108
109
    /**
110
     * Returns the current element
111
     * @link http://www.php.net/manual/en/mongocursor.current.php
112
     * @return array
113
     */
114 19
    public function current()
115
    {
116 19
        $this->startedIterating = true;
117 19
        $document = $this->ensureIterator()->current();
118 19
        if ($document !== null) {
119 19
            $document = TypeConverter::toLegacy($document);
120 19
        }
121
122 19
        return $document;
123
    }
124
125
    /**
126
     * Returns the current result's _id
127
     * @link http://www.php.net/manual/en/mongocursor.key.php
128
     * @return string The current result's _id as a string.
129
     */
130 10
    public function key()
131
    {
132 10
        return $this->ensureIterator()->key();
133
    }
134
135
    /**
136
     * Advances the cursor to the next result, and returns that result
137
     * @link http://www.php.net/manual/en/mongocursor.next.php
138
     * @throws \MongoConnectionException
139
     * @throws \MongoCursorTimeoutException
140
     * @return array Returns the next object
141
     */
142 19
    public function next()
143
    {
144 19
        if (!$this->startedIterating) {
145 1
            $this->ensureIterator();
146 1
            $this->startedIterating = true;
147 1
        } else {
148 19
            $this->ensureIterator()->next();
149
        }
150
151 19
        return $this->current();
152
    }
153
154
    /**
155
     * Returns the cursor to the beginning of the result set
156
     * @throws \MongoConnectionException
157
     * @throws \MongoCursorTimeoutException
158
     * @return void
159
     */
160 36
    public function rewind()
161
    {
162
        // We can recreate the cursor to allow it to be rewound
163 36
        $this->reset();
164 36
        $this->startedIterating = true;
165 36
        $this->ensureIterator()->rewind();
166 34
    }
167
168
    /**
169
     * Checks if the cursor is reading a valid result.
170
     * @link http://www.php.net/manual/en/mongocursor.valid.php
171
     * @return boolean If the current result is not null.
172
     */
173 34
    public function valid()
174
    {
175 34
        return $this->ensureIterator()->valid();
176
    }
177
178
    /**
179
     * Limits the number of elements returned in one batch.
180
     *
181
     * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
182
     * @param int $batchSize The number of results to return per batch
183
     * @return $this Returns this cursor.
184
     */
185 1
    public function batchSize($batchSize)
186
    {
187 1
        $this->batchSize = $batchSize;
188
189 1
        return $this;
190
    }
191
192
    /**
193
     * Checks if there are documents that have not been sent yet from the database for this cursor
194
     * @link http://www.php.net/manual/en/mongocursor.dead.php
195
     * @return boolean Returns if there are more results that have not been sent to the client, yet.
196
     */
197
    public function dead()
198
    {
199
        return $this->ensureCursor()->isDead();
200
    }
201
202
    /**
203
     * @return array
204
     */
205 2
    public function info()
206
    {
207 2
        return $this->getCursorInfo() + $this->getIterationInfo();
208
    }
209
210
    /**
211
     * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
212
     * @param string $readPreference
213
     * @param array $tags
214
     * @return $this Returns this cursor.
215
     */
216 42
    public function setReadPreference($readPreference, $tags = null)
217
    {
218 42
        $this->setReadPreferenceFromParameters($readPreference, $tags);
219
220 42
        return $this;
221
    }
222
223
    /**
224
     * Sets a client-side timeout for this query
225
     * @link http://www.php.net/manual/en/mongocursor.timeout.php
226
     * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
227
     * @return $this Returns this cursor
228
     */
229
    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...
230
    {
231
        $this->notImplemented();
232
    }
233
234
    /**
235
     * Applies all options set on the cursor, overwriting any options that have already been set
236
     *
237
     * @param array $optionNames Array of option names to be applied (will be read from properties)
238
     * @return array
239
     */
240 39
    protected function getOptions($optionNames = null)
241
    {
242 39
        $options = [];
243
244 39
        if ($optionNames === null) {
245 37
            $optionNames = $this->optionNames;
246 37
        }
247
248 39
        foreach ($optionNames as $option) {
249 39
            $converter = 'convert' . ucfirst($option);
250 39
            $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
251
252 39
            if ($value === null) {
253 39
                continue;
254
            }
255
256 38
            $options[$option] = $value;
257 39
        }
258
259 39
        return $options;
260
    }
261
262
    /**
263
     * @return \Generator
264
     */
265 37
    protected function ensureIterator()
266
    {
267 37
        if ($this->iterator === null) {
268
            // MongoDB\Driver\Cursor needs to be wrapped into a \Generator so that a valid \Iterator with working implementations of
269
            // next, current, valid, key and rewind is returned. These methods don't work if we wrap the Cursor inside an \IteratorIterator
270 37
            $this->iterator = $this->wrapTraversable($this->ensureCursor());
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->wrapTraversable($this->ensureCursor()) of type object<Generator> is incompatible with the declared type object<IteratorIterator> of property $iterator.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
271 35
        }
272
273 35
        return $this->iterator;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->iterator; of type Generator|IteratorIterator adds the type IteratorIterator to the return on line 273 which is incompatible with the return type documented by Alcaeus\MongoDbAdapter\A...tCursor::ensureIterator of type Generator.
Loading history...
274
    }
275
276
    /**
277
     * @param \Traversable $traversable
278
     * @return \Generator
279
     */
280 35
    private function wrapTraversable(\Traversable $traversable)
281
    {
282 35
        foreach ($traversable as $key => $value) {
283 19
            if ($this instanceof \MongoCursor &&
284 19
                isset($value->_id) &&
285 10
                ($value->_id instanceof \MongoDB\BSON\ObjectID || !is_object($value->_id))
1 ignored issue
show
Bug introduced by
The class MongoDB\BSON\ObjectID does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
286 19
            ) {
287 10
                $key = (string) $value->_id;
288 10
            }
289 19
            yield $key => $value;
290 35
        }
291 35
    }
292
293
    /**
294
     * @throws \MongoCursorException
295
     */
296 15
    protected function errorIfOpened()
297
    {
298 15
        if ($this->cursor === null) {
299 15
            return;
300
        }
301
302
        throw new \MongoCursorException('cannot modify cursor after beginning iteration.');
303
    }
304
305
    /**
306
     * @return array
307
     */
308 2
    protected function getIterationInfo()
309
    {
310
        $iterationInfo = [
311 2
            'started_iterating' => $this->cursor !== null,
312 2
        ];
313
314 2
        if ($this->cursor !== null) {
315 2
            switch ($this->cursor->getServer()->getType()) {
316 2
                case \MongoDB\Driver\Server::TYPE_RS_ARBITER:
317
                    $typeString = 'ARBITER';
318
                    break;
319 2
                case \MongoDB\Driver\Server::TYPE_MONGOS:
320
                    $typeString = 'MONGOS';
321
                    break;
322 2
                case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
323
                    $typeString = 'PRIMARY';
324
                    break;
325 2
                case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
326
                    $typeString = 'SECONDARY';
327
                    break;
328 2
                default:
329 2
                    $typeString = 'STANDALONE';
330 2
            }
331
332
            $iterationInfo += [
333 2
                'id' => (string) $this->cursor->getId(),
334 2
                'at' => null, // @todo Complete info for cursor that is iterating
335 2
                'numReturned' => null, // @todo Complete info for cursor that is iterating
336 2
                'server' => null, // @todo Complete info for cursor that is iterating
337 2
                'host' => $this->cursor->getServer()->getHost(),
338 2
                'port' => $this->cursor->getServer()->getPort(),
339 2
                'connection_type_desc' => $typeString,
340
            ];
341 2
        }
342
343 2
        return $iterationInfo;
344
    }
345
346
    /**
347
     * @throws \Exception
348
     */
349
    protected function notImplemented()
350
    {
351
        throw new \Exception('Not implemented');
352
    }
353
354
    /**
355
     * Clears the cursor
356
     *
357
     * This is generic but implemented as protected since it's only exposed in MongoCursor
358
     */
359 37
    protected function reset()
360
    {
361 37
        $this->startedIterating = false;
362 37
        $this->cursor = null;
363 37
        $this->iterator = null;
364 37
    }
365
}
366