Commit 0d5b7c48 authored by Jeremy Mikola's avatar Jeremy Mikola

PHPLIB-81: Move CachingIterator to MongoDB\Model

parent 9f677306
<?php <?php
/*
namespace MongoDB; * Copyright 2017 MongoDB, Inc.
*
class CachingIterator implements \Iterator, \Countable * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use Countable;
use Generator;
use Iterator;
use Traversable;
/**
* Iterator for wrapping a Traversable and caching its results.
*
* By caching results, this iterators allows a Traversable to be counted and
* rewound multiple times, even if the wrapped object does not natively support
* those operations (e.g. MongoDB\Driver\Cursor).
*
* @internal
*/
class CachingIterator implements Countable, Iterator
{ {
/** private $items;
* @var \Traversable
*/
private $iterator; private $iterator;
/**
* @var array
*/
private $items = [];
/**
* @var bool
*/
private $iteratorExhausted = false; private $iteratorExhausted = false;
/** /**
* @param \Traversable $iterator * @param Traversable $traversable
*/ */
public function __construct(\Traversable $iterator) public function __construct(Traversable $traversable)
{ {
$this->iterator = $this->wrapTraversable($iterator); $this->iterator = $this->wrapTraversable($traversable);
$this->storeCurrentItem(); $this->storeCurrentItem();
} }
/** /**
* @return int * @see http://php.net/countable.count
* @return integer
*/ */
public function count() public function count()
{ {
$this->exhaustIterator(); $this->exhaustIterator();
return count($this->items); return count($this->items);
} }
/** /**
* @see http://php.net/iterator.current
* @return mixed * @return mixed
*/ */
public function current() public function current()
...@@ -46,6 +67,7 @@ class CachingIterator implements \Iterator, \Countable ...@@ -46,6 +67,7 @@ class CachingIterator implements \Iterator, \Countable
} }
/** /**
* @see http://php.net/iterator.mixed
* @return mixed * @return mixed
*/ */
public function key() public function key()
...@@ -54,11 +76,12 @@ class CachingIterator implements \Iterator, \Countable ...@@ -54,11 +76,12 @@ class CachingIterator implements \Iterator, \Countable
} }
/** /**
* @see http://php.net/iterator.next
* @return void * @return void
*/ */
public function next() public function next()
{ {
if (! $this->iteratorExhausted) { if ( ! $this->iteratorExhausted) {
$this->iterator->next(); $this->iterator->next();
$this->storeCurrentItem(); $this->storeCurrentItem();
} }
...@@ -67,6 +90,7 @@ class CachingIterator implements \Iterator, \Countable ...@@ -67,6 +90,7 @@ class CachingIterator implements \Iterator, \Countable
} }
/** /**
* @see http://php.net/iterator.rewind
* @return void * @return void
*/ */
public function rewind() public function rewind()
...@@ -76,7 +100,9 @@ class CachingIterator implements \Iterator, \Countable ...@@ -76,7 +100,9 @@ class CachingIterator implements \Iterator, \Countable
} }
/** /**
* @return bool *
* @see http://php.net/iterator.valid
* @return boolean
*/ */
public function valid() public function valid()
{ {
...@@ -84,21 +110,23 @@ class CachingIterator implements \Iterator, \Countable ...@@ -84,21 +110,23 @@ class CachingIterator implements \Iterator, \Countable
} }
/** /**
* Ensures the original iterator is fully consumed and all items cached * Ensures that the inner iterator is fully consumed and cached.
*/ */
private function exhaustIterator() private function exhaustIterator()
{ {
while (!$this->iteratorExhausted) { while ( ! $this->iteratorExhausted) {
$this->next(); $this->next();
} }
} }
/** /**
* Stores the current item * Stores the current item in the cache.
*/ */
private function storeCurrentItem() private function storeCurrentItem()
{ {
if (null === $key = $this->iterator->key()) { $key = $this->iterator->key();
if ($key === null) {
return; return;
} }
...@@ -106,14 +134,17 @@ class CachingIterator implements \Iterator, \Countable ...@@ -106,14 +134,17 @@ class CachingIterator implements \Iterator, \Countable
} }
/** /**
* @param \Traversable $traversable * Wraps the Traversable with a Generator.
* @return \Generator *
* @param Traversable $traversable
* @return Generator
*/ */
private function wrapTraversable(\Traversable $traversable) private function wrapTraversable(Traversable $traversable)
{ {
foreach ($traversable as $key => $value) { foreach ($traversable as $key => $value) {
yield $key => $value; yield $key => $value;
} }
$this->iteratorExhausted = true; $this->iteratorExhausted = true;
} }
} }
<?php <?php
namespace MongoDB\Tests; namespace MongoDB\Tests\Model;
use MongoDB\CachingIterator; use MongoDB\Model\CachingIterator;
class CachingIteratorTest extends \PHPUnit_Framework_TestCase class CachingIteratorTest extends \PHPUnit_Framework_TestCase
{ {
/** /**
* Sanity check for all following tests * Sanity check for all following tests.
*
* @expectedException \Exception * @expectedException \Exception
* @expectedExceptionMessage Cannot traverse an already closed generator * @expectedExceptionMessage Cannot traverse an already closed generator
*/ */
public function testTraverseGeneratorConsumesIt() public function testTraversingGeneratorConsumesIt()
{ {
$iterator = $this->getTraversable([1, 2, 3]); $iterator = $this->getTraversable([1, 2, 3]);
$this->assertSame([1, 2, 3], iterator_to_array($iterator)); $this->assertSame([1, 2, 3], iterator_to_array($iterator));
$this->assertSame([1, 2, 3], iterator_to_array($iterator)); $this->assertSame([1, 2, 3], iterator_to_array($iterator));
} }
public function testIterateOverItems() public function testIteration()
{ {
$iterator = new CachingIterator($this->getTraversable([1, 2, 3])); $iterator = new CachingIterator($this->getTraversable([1, 2, 3]));
$expectedKey = 0; $expectedKey = 0;
$expectedItem = 1; $expectedItem = 1;
foreach ($iterator as $key => $item) { foreach ($iterator as $key => $item) {
$this->assertSame($expectedKey++, $key); $this->assertSame($expectedKey++, $key);
$this->assertSame($expectedItem++, $item); $this->assertSame($expectedItem++, $item);
} }
$this->assertFalse($iterator->valid()); $this->assertFalse($iterator->valid());
} }
public function testIteratePartiallyThenRewind() public function testRewindAfterPartialIteration()
{ {
$iterator = new CachingIterator($this->getTraversable([1, 2, 3])); $iterator = new CachingIterator($this->getTraversable([1, 2, 3]));
...@@ -47,7 +50,7 @@ class CachingIteratorTest extends \PHPUnit_Framework_TestCase ...@@ -47,7 +50,7 @@ class CachingIteratorTest extends \PHPUnit_Framework_TestCase
$this->assertCount(3, $iterator); $this->assertCount(3, $iterator);
} }
public function testCountAfterPartiallyIterating() public function testCountAfterPartialIteration()
{ {
$iterator = new CachingIterator($this->getTraversable([1, 2, 3])); $iterator = new CachingIterator($this->getTraversable([1, 2, 3]));
$iterator->next(); $iterator->next();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment