1.4. Iterator¶
1.4.1. Intent¶
According to the Gang of Four, the Iterator pattern aims to “provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 257).
1.4.2. When to use it?¶
The Iterator pattern should be used in various cases:
- you need to have access to an element of a collection without exposing the internal structure of that collection
- your application needs to support multiple simultaneous traversals of a collection.
- you want to provide a uniform interface for traversing different types of collections
1.4.3. Diagram¶
Created using PhpStorm and yFiles.
1.4.3.1. Using Standard PHP Library (SPL)¶
1.4.3.2. Without Standard PHP Library (SPL)¶
1.4.4. Implementation¶
Bottle.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
namespace Phpatterns\Behavioral\Iterator;
class Bottle
{
/** @var string */
private $name;
/** @var string */
private $type;
public function __construct($name, $type)
{
$this->name = $name;
$this->type = $type;
}
/**
* @return string
*/
public function __toString()
{
return "$this->name - $this->type";
}
}
|
BottleCrate.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php
namespace Phpatterns\Behavioral\Iterator;
class BottleCrate
{
/** @var Bottle[] */
private $bottles;
public function __construct()
{
$this->bottles = [];
}
public function getCount()
{
return count($this->bottles);
}
/**
* @param int $bottleNumber
* @return null|Bottle
*/
public function getBottle($bottleNumber)
{
if ($bottleNumber < $this->getCount()) {
return $this->bottles[$bottleNumber];
}
return null;
}
/**
* @param Bottle $bottle
*/
public function addBottle(Bottle $bottle)
{
if (!in_array($bottle, $this->bottles, true)) {
$this->bottles[] = $bottle;
}
}
/**
* @param Bottle $bottle
*/
public function removeBottle(Bottle $bottle)
{
$key = array_search($bottle, $this->bottles, true);
if ($key !== false) {
unset($this->bottles[$key]);
}
}
}
|
1.4.4.1. Using Standard PHP Library (SPL)¶
BottleCrateIterator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <?php
namespace Phpatterns\Behavioral\Iterator\UsingSPL;
use Phpatterns\Behavioral\Iterator;
/**
* We are using the Iterator interface provided by PHP
* @link http://php.net/manual/en/class.iterator.php
*/
class BottleCrateIterator implements \Iterator
{
/** @var Iterator\BottleCrate */
private $bottleCrate;
/** @var int */
private $index;
public function __construct(Iterator\BottleCrate $bottleCrate)
{
$this->bottleCrate = $bottleCrate;
$this->index = 0;
}
/**
* Return the current Bottle
* @return Iterator\Bottle
*/
public function current()
{
return $this->bottleCrate->getBottle($this->index);
}
/**
* Move forward to next Bottle
*/
public function next()
{
$this->index++;
}
/**
* Return the key of the current Bottle
* @return int
*/
public function key()
{
return $this->index;
}
/**
* Checks if current position is valid
* @return bool
*/
public function valid()
{
return null !== $this->bottleCrate->getBottle($this->index);
}
/**
* Rewind the Iterator to the first Bottle
*/
public function rewind()
{
$this->index = 0;
}
}
|
1.4.4.2. Without Standard PHP Library (SPL)¶
IteratorInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
namespace Phpatterns\Behavioral\Iterator\WithoutSPL;
interface IteratorInterface
{
/**
* Rewind the Iterator to the first element
*/
public function currentItem();
/**
* Return the current element
*/
public function first();
/**
* Return the key of the current element
*/
public function isDone();
/**
* Move forward to next element
*/
public function next();
}
|
BottleCrateIterator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php
namespace Phpatterns\Behavioral\Iterator\WithoutSPL;
use Phpatterns\Behavioral\Iterator;
class BottleCrateIterator implements IteratorInterface
{
/** @var Iterator\BottleCrate */
private $bottleCrate;
/** @var int */
private $index;
public function __construct(Iterator\BottleCrate $bottleCrate)
{
$this->bottleCrate = $bottleCrate;
}
/**
* @return Iterator\Bottle
*/
public function currentItem()
{
if (!$this->isDone()) {
return $this->bottleCrate->getBottle($this->index);
}
throw new \OutOfBoundsException("No more bottle in the crate.");
}
public function first()
{
$this->index = 0;
}
public function isDone()
{
return $this->index >= $this->bottleCrate->getCount();
}
public function next()
{
$this->index++;
}
}
|
1.4.5. Tests¶
BottleCrateTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <?php
namespace Test\Phpatterns\Behavioral\Iterator;
use Phpatterns\Behavioral\Iterator;
class BottleCrateTest extends \PHPUnit_Framework_TestCase
{
/** @var Iterator\BottleCrate */
private $bottleCrate;
protected function setUp()
{
$this->bottleCrate = new Iterator\BottleCrate();
$this->bottleCrate->addBottle(new Iterator\Bottle('Coca Cola', 'Soda'));
$this->bottleCrate->addBottle(new Iterator\Bottle('Château Rayas', 'Wine'));
$this->bottleCrate->addBottle(new Iterator\Bottle('Dom Pérignon', 'Champagne'));
}
public function testCount()
{
$this->assertEquals(3, $this->bottleCrate->getCount());
}
public function testAddBottleToTheCrate()
{
$this->bottleCrate->addBottle(new Iterator\Bottle('Coca Cola', 'Soda'));
$this->assertEquals(4, $this->bottleCrate->getCount());
}
public function testRemoveExistingBottleFromTheCrate()
{
$this->bottleCrate->removeBottle($this->bottleCrate->getBottle(1));
$this->assertEquals(2, $this->bottleCrate->getCount());
}
public function testRemoveNonExistingBottleFromTheCrate()
{
$this->bottleCrate->removeBottle(new Iterator\Bottle('Coca Cola', 'Soda'));
$this->assertEquals(3, $this->bottleCrate->getCount());
}
public function testGetBottle()
{
$this->assertEquals(
'Château Rayas - Wine',
$this->bottleCrate->getBottle(1)
);
}
}
|
1.4.5.1. Using Standard PHP Library (SPL)¶
IteratorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <?php
namespace Test\Phpatterns\Behavioral\Iterator\UsingSPL;
use Phpatterns\Behavioral\Iterator;
use Phpatterns\Behavioral\Iterator\UsingSPL;
class IteratorTest extends \PHPUnit_Framework_TestCase
{
/** @var Iterator\BottleCrate */
private $bottleCrate;
protected function setUp()
{
$this->bottleCrate = new Iterator\BottleCrate();
$this->bottleCrate->addBottle(new Iterator\Bottle('Coca Cola', 'Soda'));
$this->bottleCrate->addBottle(new Iterator\Bottle('Château Rayas', 'Wine'));
$this->bottleCrate->addBottle(new Iterator\Bottle('Dom Pérignon', 'Champagne'));
}
/**
* Testing the iterator mechanism
*/
public function testIteratorMechanism()
{
$bottleDescriptions = ['Coca Cola - Soda', 'Château Rayas - Wine', 'Dom Pérignon - Champagne'];
$bottleCrateIterator = new UsingSPL\BottleCrateIterator($this->bottleCrate);
while ($bottleCrateIterator->valid()) {
$this->assertSame(
$bottleDescriptions[$bottleCrateIterator->key()],
$bottleCrateIterator->current()->__toString()
);
$bottleCrateIterator->next();
}
$this->assertEquals(3, $bottleCrateIterator->key());
$bottleCrateIterator->rewind();
$this->assertEquals(0, $bottleCrateIterator->key());
}
}
|
1.4.5.2. Without Standard PHP Library (SPL)¶
IteratorTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?php
namespace Test\Phpatterns\Behavioral\Iterator\WithoutSPL;
use Phpatterns\Behavioral\Iterator;
use Phpatterns\Behavioral\Iterator\WithoutSPL;
class IteratorTest extends \PHPUnit_Framework_TestCase
{
/** @var Iterator\BottleCrate */
private $bottleCrate;
protected function setUp()
{
$this->bottleCrate = new Iterator\BottleCrate();
$this->bottleCrate->addBottle(new Iterator\Bottle('Coca Cola', 'Soda'));
$this->bottleCrate->addBottle(new Iterator\Bottle('Château Rayas', 'Wine'));
$this->bottleCrate->addBottle(new Iterator\Bottle('Dom Pérignon', 'Champagne'));
}
/**
* Testing the iterator mechanism
*/
public function testIteratorMechanism()
{
$bottleDescriptions = ['Coca Cola - Soda', 'Château Rayas - Wine', 'Dom Pérignon - Champagne'];
$bottleCrateIterator = new WithoutSPL\BottleCrateIterator($this->bottleCrate);
for ($bottleCrateIterator->first(); !$bottleCrateIterator->isDone(); $bottleCrateIterator->next()) {
$this->assertSame(
array_shift($bottleDescriptions),
$bottleCrateIterator->currentItem()->__toString()
);
}
$this->setExpectedException('OutOfBoundsException', 'No more bottle in the crate.');
$bottleCrateIterator->currentItem();
}
}
|