diff --git a/src/Model/BSONIterator.php b/src/Model/BSONIterator.php new file mode 100644 index 000000000..d54c123a6 --- /dev/null +++ b/src/Model/BSONIterator.php @@ -0,0 +1,135 @@ +buffer = $data; + $this->bufferLength = strlen($data); + $this->options = $options; + } + + /** + * @see http://php.net/iterator.current + * @return mixed + */ + public function current() + { + return $this->current; + } + + /** + * @see http://php.net/iterator.key + * @return mixed + */ + public function key() + { + return $this->key; + } + + /** + * @see http://php.net/iterator.next + * @return void + */ + public function next() + { + $this->key++; + $this->current = null; + $this->advance(); + } + + /** + * @see http://php.net/iterator.rewind + * @return void + */ + public function rewind() + { + $this->key = 0; + $this->position = 0; + $this->current = null; + $this->advance(); + } + + /** + * @see http://php.net/iterator.valid + * @return boolean + */ + public function valid() + { + return $this->current !== null; + } + + private function advance() + { + if ($this->position === $this->bufferLength) { + return; + } + + if (($this->bufferLength - $this->position) < self::BSON_SIZE) { + throw new UnexpectedValueException(sprintf('Expected at least %d bytes; %d remaining', self::BSON_SIZE, $this->bufferLength - $this->position)); + } + + list(,$documentLength) = unpack('V', substr($this->buffer, $this->position, self::BSON_SIZE)); + + if (($this->bufferLength - $this->position) < $documentLength) { + throw new UnexpectedValueException(sprintf('Expected %d bytes; %d remaining', $documentLength, $this->bufferLength - $this->position)); + } + + $this->current = \MongoDB\BSON\toPHP(substr($this->buffer, $this->position, $documentLength), $this->options['typeMap']); + $this->position += $documentLength; + } +} diff --git a/tests/Model/BSONIteratorTest.php b/tests/Model/BSONIteratorTest.php new file mode 100644 index 000000000..182f461cd --- /dev/null +++ b/tests/Model/BSONIteratorTest.php @@ -0,0 +1,133 @@ + $typeMap]); + + $results = iterator_to_array($bsonIt); + + $this->assertEquals($expectedDocuments, $results); + } + + public function provideTypeMapOptionsAndExpectedDocuments() + { + return [ + [ + null, + implode(array_map( + 'MongoDB\BSON\fromPHP', + [ + ['_id' => 1, 'x' => ['foo' => 'bar']], + ['_id' => 3, 'x' => ['foo' => 'bar']], + ] + )), + [ + (object) ['_id' => 1, 'x' => (object) ['foo' => 'bar']], + (object) ['_id' => 3, 'x' => (object) ['foo' => 'bar']], + ] + ], + [ + ['root' => 'array', 'document' => 'array'], + implode(array_map( + 'MongoDB\BSON\fromPHP', + [ + ['_id' => 1, 'x' => ['foo' => 'bar']], + ['_id' => 3, 'x' => ['foo' => 'bar']], + ] + )), + [ + ['_id' => 1, 'x' => ['foo' => 'bar']], + ['_id' => 3, 'x' => ['foo' => 'bar']], + ] + ], + [ + ['root' => 'object', 'document' => 'array'], + implode(array_map( + 'MongoDB\BSON\fromPHP', + [ + ['_id' => 1, 'x' => ['foo' => 'bar']], + ['_id' => 3, 'x' => ['foo' => 'bar']], + ] + )), + [ + (object) ['_id' => 1, 'x' => ['foo' => 'bar']], + (object) ['_id' => 3, 'x' => ['foo' => 'bar']], + ] + ], + [ + ['root' => 'array', 'document' => 'stdClass'], + implode(array_map( + 'MongoDB\BSON\fromPHP', + [ + ['_id' => 1, 'x' => ['foo' => 'bar']], + ['_id' => 3, 'x' => ['foo' => 'bar']], + ] + )), + [ + ['_id' => 1, 'x' => (object) ['foo' => 'bar']], + ['_id' => 3, 'x' => (object) ['foo' => 'bar']], + ] + ], + ]; + } + + public function testCannotReadLengthFromFirstDocument() + { + $binaryString = substr(\MongoDB\BSON\fromPHP([]), 0, 3); + + $bsonIt = new BSONIterator($binaryString); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Expected at least 4 bytes; 3 remaining'); + $bsonIt->rewind(); + } + + public function testCannotReadLengthFromSubsequentDocument() + { + $binaryString = \MongoDB\BSON\fromPHP([]) . substr(\MongoDB\BSON\fromPHP([]), 0, 3); + + $bsonIt = new BSONIterator($binaryString); + $bsonIt->rewind(); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Expected at least 4 bytes; 3 remaining'); + $bsonIt->next(); + } + + public function testCannotReadFirstDocument() + { + $binaryString = substr(\MongoDB\BSON\fromPHP([]), 0, 4); + + $bsonIt = new BSONIterator($binaryString); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Expected 5 bytes; 4 remaining'); + $bsonIt->rewind(); + } + + public function testCannotReadSecondDocument() + { + $binaryString = \MongoDB\BSON\fromPHP([]) . substr(\MongoDB\BSON\fromPHP([]), 0, 4); + + $bsonIt = new BSONIterator($binaryString); + $bsonIt->rewind(); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Expected 5 bytes; 4 remaining'); + $bsonIt->next(); + } +}