Skip to content

Commit

Permalink
Adding an iterator component
Browse files Browse the repository at this point in the history
  • Loading branch information
mtdowling committed Oct 15, 2012
1 parent e1a5cde commit 95e1a46
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/Guzzle/Iterator/ChunkedIterator.php
@@ -0,0 +1,66 @@
<?php

namespace Guzzle\Iterator;

/**
* Pulls out chunks from an inner iterator and yields the chunks as arrays
*/
class ChunkedIterator extends \IteratorIterator
{
/**
* @var int Size of each chunk
*/
protected $chunkSize;

/**
* @var array Current chunk
*/
protected $chunk;

/**
* @param \Traversable $iterator Traversable iterator
* @param int $chunkSize Size to make each chunk
*/
public function __construct(\Traversable $iterator, $chunkSize)
{
parent::__construct($iterator);
$this->chunkSize = $chunkSize;
}

/**
* {@inheritdoc}
*/
public function rewind()
{
$this->next();
}

/**
* {@inheritdoc}
*/
public function next()
{
$this->chunk = array();
$inner = $this->getInnerIterator();
for ($i = 0; $i < $this->chunkSize && $inner->valid(); $i++) {
$this->chunk[] = $inner->current();
$inner->next();
}
}

/**
* {@inheritdoc}
*/
public function current()
{
return $this->chunk;
}

/**
* {@inheritdoc}
*/
public function valid()
{
return !empty($this->chunk);
}
}
39 changes: 39 additions & 0 deletions src/Guzzle/Iterator/FilterIterator.php
@@ -0,0 +1,39 @@
<?php

namespace Guzzle\Iterator;

/**
* Filters values using a callback
*
* Used when PHP 5.4's {@see \CallbackFilterIterator} is not available
*/
class FilterIterator extends \FilterIterator
{
/**
* @var mixed Callback used for filtering
*/
protected $callback;

/**
* @param \Traversable $iterator Traversable iterator
* @param array|\Closure $callback Callback used for filtering. Return true to keep or false to filter.
*
* @throws \InvalidArgumentException if the callback if not callable
*/
public function __construct(\Traversable $iterator, $callback)
{
parent::__construct($iterator);
if (!is_callable($callback)) {
throw new \InvalidArgumentException('The callback must be callable');
}
$this->callback = $callback;
}

/**
* {@inheritdoc}
*/
public function accept()
{
return call_user_func($this->callback, $this->current());
}
}
37 changes: 37 additions & 0 deletions src/Guzzle/Iterator/MapIterator.php
@@ -0,0 +1,37 @@
<?php

namespace Guzzle\Iterator;

/**
* Maps values before yielding
*/
class MapIterator extends \IteratorIterator
{
/**
* @var mixed Callback
*/
protected $callback;

/**
* @param \Traversable $iterator Traversable iterator
* @param array|\Closure $callback Callback used for iterating
*
* @throws \InvalidArgumentException if the callback if not callable
*/
public function __construct(\Traversable $iterator, $callback)
{
parent::__construct($iterator);
if (!is_callable($callback)) {
throw new \InvalidArgumentException('The callback must be callable');
}
$this->callback = $callback;
}

/**
* {@inheritdoc}
*/
public function current()
{
return call_user_func($this->callback, parent::current());
}
}
27 changes: 27 additions & 0 deletions src/Guzzle/Iterator/MethodProxyIterator.php
@@ -0,0 +1,27 @@
<?php

namespace Guzzle\Iterator;

/**
* Proxies missing method calls to the innermost iterator
*/
class MethodProxyIterator extends \IteratorIterator
{
/**
* Proxy method calls to the wrapped iterator
*
* @param string $name Name of the method
* @param array $args Arguments to proxy
*
* @return mixed
*/
public function __call($name, array $args)
{
$i = $this->getInnerIterator();
while ($i instanceof \OuterIterator) {
$i = $i->getInnerIterator();
}

return call_user_func_array(array($i, $name), $args);
}
}
41 changes: 41 additions & 0 deletions src/Guzzle/Iterator/README.md
@@ -0,0 +1,41 @@
Guzzle Iterator
===============

[![Build Status](https://secure.travis-ci.org/guzzle/iterator.png?branch=master)](http://travis-ci.org/guzzle/guzzle)

Component library that provides useful Iterators and Iterator decorators

- ChunkedIterator: Pulls out chunks from an inner iterator and yields the chunks as arrays
- FilterIterator: Used when PHP 5.4's CallbackFilterIterator is not available
- MapIterator: Maps values before yielding
- MethodProxyIterator: Proxies missing method calls to the innermost iterator

### Installing via Composer

The recommended way to install is through [Composer](http://getcomposer.org).

1. Add ``guzzle/iterator`` as a dependency in your project's ``composer.json`` file:

{
"require": {
"guzzle/iterator": "*"
}
}

Consider tightening your dependencies to a known version when deploying mission critical applications (e.g. ``2.7.*``).

2. Download and install Composer:

curl -s http://getcomposer.org/installer | php

3. Install your dependencies:

php composer.phar install

4. Require Composer's autoloader

Composer also prepares an autoload file that's capable of autoloading all of the classes in any of the libraries that it downloads. To use it, just add the following line to your code's bootstrap process:

require 'vendor/autoload.php';

You can find out more on how to install Composer, configure autoloading, and other best-practices for defining dependencies at [getcomposer.org](http://getcomposer.org).
23 changes: 23 additions & 0 deletions src/Guzzle/Iterator/composer.json
@@ -0,0 +1,23 @@
{
"name": "guzzle/iterator",
"type": "component",
"description": "Provides helpful iterators and iterator decorators",
"keywords": ["iterator", "guzzle"],
"homepage": "http://guzzlephp.org/",
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.3.2"
},
"autoload": {
"psr-0": {
"Guzzle\\Iterator": "src/"
}
}
}
31 changes: 31 additions & 0 deletions tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php
@@ -0,0 +1,31 @@
<?php

namespace Guzzle\Tests\Iterator;

use Guzzle\Iterator\ChunkedIterator;

/**
* @covers Guzzle\Iterator\ChunkedIterator
*/
class ChunkedIteratorTest extends \PHPUnit_Framework_TestCase
{
public function testChunksIterator()
{
$chunked = new ChunkedIterator(new \ArrayIterator(range(0, 100)), 10);
$chunks = iterator_to_array($chunked, false);
$this->assertEquals(11, count($chunks));
foreach ($chunks as $j => $chunk) {
$this->assertEquals(range($j * 10, min(100, $j * 10 + 9)), $chunk);
}
}

public function testChunksIteratorWithOddValues()
{
$chunked = new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2);
$chunks = iterator_to_array($chunked, false);
$this->assertEquals(3, count($chunks));
$this->assertEquals(array(1, 2), $chunks[0]);
$this->assertEquals(array(3, 4), $chunks[1]);
$this->assertEquals(array(5), $chunks[2]);
}
}
28 changes: 28 additions & 0 deletions tests/Guzzle/Tests/Iterator/FilterIteratorTest.php
@@ -0,0 +1,28 @@
<?php

namespace Guzzle\Tests\Iterator;

use Guzzle\Iterator\FilterIterator;

/**
* @covers Guzzle\Iterator\FilterIterator
*/
class FilterIteratorTest extends \PHPUnit_Framework_TestCase
{
public function testFiltersValues()
{
$i = new FilterIterator(new \ArrayIterator(range(0, 100)), function ($value) {
return $value % 2;
});

$this->assertEquals(range(1, 99, 2), iterator_to_array($i, false));
}

/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesCallable()
{
$i = new FilterIterator(new \ArrayIterator(), new \stdClass());
}
}
28 changes: 28 additions & 0 deletions tests/Guzzle/Tests/Iterator/MapIteratorTest.php
@@ -0,0 +1,28 @@
<?php

namespace Guzzle\Tests\Iterator;

use Guzzle\Iterator\MapIterator;

/**
* @covers Guzzle\Iterator\MapIterator
*/
class MapIteratorTest extends \PHPUnit_Framework_TestCase
{
public function testFiltersValues()
{
$i = new MapIterator(new \ArrayIterator(range(0, 100)), function ($value) {
return $value * 10;
});

$this->assertEquals(range(0, 1000, 10), iterator_to_array($i, false));
}

/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesCallable()
{
$i = new MapIterator(new \ArrayIterator(), new \stdClass());
}
}
28 changes: 28 additions & 0 deletions tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php
@@ -0,0 +1,28 @@
<?php

namespace Guzzle\Tests\Iterator;

use Guzzle\Iterator\MethodProxyIterator;
use Guzzle\Iterator\ChunkedIterator;

/**
* @covers Guzzle\Iterator\MethodProxyIterator
*/
class MethodProxyIteratorTest extends \PHPUnit_Framework_TestCase
{
public function testProxiesMagicCallsToInnermostIterator()
{
$i = new \ArrayIterator();
$proxy = new MethodProxyIterator(new MethodProxyIterator(new MethodProxyIterator($i)));
$proxy->append('a');
$proxy->append('b');
$this->assertEquals(array('a', 'b'), $i->getArrayCopy());
$this->assertEquals(array('a', 'b'), $proxy->getArrayCopy());
}

public function testUsesInnerIterator()
{
$i = new MethodProxyIterator(new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2));
$this->assertEquals(3, count(iterator_to_array($i, false)));
}
}

0 comments on commit 95e1a46

Please sign in to comment.