Jelajahi Sumber

Fix MongoCursor next/getNext behaviour

Because of backwards compatibility reasons MongoCursor's implementation of next/getNext is a bit strange and does not match the implementations of "normal" iterators:

* getNext is an alias for next
* On the first call, when the cursor has not been used yet, next returns current
* On all subsequent calls next advances the cursor and then returns current
Bastian Hofmann 10 tahun lalu
induk
melakukan
8358e9a8f0

+ 18 - 3
lib/Alcaeus/MongoDbAdapter/AbstractCursor.php

@@ -62,6 +62,11 @@ abstract class AbstractCursor
     protected $ns;
 
     /**
+     * @var bool
+     */
+    protected $startedIterating = false;
+
+    /**
      * @var array
      */
     protected $optionNames = [
@@ -108,6 +113,7 @@ abstract class AbstractCursor
      */
     public function current()
     {
+        $this->startedIterating = true;
         $document = $this->ensureIterator()->current();
         if ($document !== null) {
             $document = TypeConverter::toLegacy($document);
@@ -127,15 +133,22 @@ abstract class AbstractCursor
     }
 
     /**
-     * Advances the cursor to the next result
+     * Advances the cursor to the next result, and returns that result
      * @link http://www.php.net/manual/en/mongocursor.next.php
      * @throws \MongoConnectionException
      * @throws \MongoCursorTimeoutException
-     * @return void
+     * @return array Returns the next object
      */
     public function next()
     {
-        $this->ensureIterator()->next();
+        if (!$this->startedIterating) {
+            $this->ensureIterator();
+            $this->startedIterating = true;
+        } else {
+            $this->ensureIterator()->next();
+        }
+
+        return $this->current();
     }
 
     /**
@@ -148,6 +161,7 @@ abstract class AbstractCursor
     {
         // We can recreate the cursor to allow it to be rewound
         $this->reset();
+        $this->startedIterating = true;
         $this->ensureIterator()->rewind();
     }
 
@@ -338,6 +352,7 @@ abstract class AbstractCursor
      */
     protected function reset()
     {
+        $this->startedIterating = false;
         $this->cursor = null;
         $this->iterator = null;
     }

+ 2 - 4
lib/Mongo/MongoCursor.php

@@ -197,7 +197,7 @@ class MongoCursor extends AbstractCursor implements Iterator
     }
 
     /**
-     * Return the next object to which this cursor points, and advance the cursor
+     * Advances the cursor to the next result, and returns that result
      * @link http://www.php.net/manual/en/mongocursor.getnext.php
      * @throws MongoConnectionException
      * @throws MongoCursorTimeoutException
@@ -205,9 +205,7 @@ class MongoCursor extends AbstractCursor implements Iterator
      */
     public function getNext()
     {
-        $this->next();
-
-        return $this->current();
+        return $this->next();
     }
 
     /**

+ 33 - 0
tests/Alcaeus/MongoDbAdapter/MongoCursorTest.php

@@ -49,6 +49,39 @@ class MongoCursorTest extends TestCase
         $cursor->count();
     }
 
+    public function testNextStartsWithFirstItem()
+    {
+        $this->prepareData();
+
+        $collection = $this->getCollection();
+        $cursor = $collection->find(['foo' => 'bar']);
+
+        $item = $cursor->getNext();
+        $this->assertNotNull($item);
+        $this->assertInstanceOf('MongoId', $item['_id']);
+        $this->assertSame('bar', $item['foo']);
+
+        $item = $cursor->getNext();
+        $this->assertNotNull($item);
+        $this->assertInstanceOf('MongoId', $item['_id']);
+        $this->assertSame('bar', $item['foo']);
+
+        $item = $cursor->getNext();
+        $this->assertNull($item);
+
+        $cursor->reset();
+
+        $item = $cursor->getNext();
+        $this->assertNotNull($item);
+        $this->assertInstanceOf('MongoId', $item['_id']);
+        $this->assertSame('bar', $item['foo']);
+
+        $item = $cursor->getNext();
+        $this->assertNotNull($item);
+        $this->assertInstanceOf('MongoId', $item['_id']);
+        $this->assertSame('bar', $item['foo']);
+    }
+
     public function testIteratorInterface()
     {
         $this->prepareData();