PhpRiot
News Archive
PhpRiot Newsletter
Your Email Address:

More information

What Iterators Can Do For You

Note: This article was originally published at Planet PHP on 1 August 2012.
Planet PHP
Basically Iterators provide a list interface for an object. Like all interfaces they are a contract how something can be used. If you use an interface it is not relevant how it is implemented - the implementation logic is encapsulated.

It is of course relevant on the integration level. A bad implementation can impact the performance of you application. Even an good implementation may need special resources (like a database). But all this does not impact how you use it. Your code using the object with the Iterator interface stays the same.

Let's start with a simple example that outputs a list.

$elements = array(
AA'line one', 'line two'
);

foreach ($lines as $key = $value) {
AAecho $key.': '.$value."\n";
}


If we transfer this into an object it would like that:

class MyProjectLineOutput {
AAprivate $_lines = NULL;

AApublic function __construct($lines) {
AAAA$this-_lines = $lines;
AA}

AApublic function __invoke() {
AAA foreach ($this-_lines as $key = $value) {
AAAAA echo $key.': '.$value."\n";
AAA }
AA}
}

$output = new MyProjectLineOutput(
AAarray(
AAAA'line one', 'line two'
AA)
);
$output();


On the first glance that looks like a lot more work but it isn't. The code includes two tasks. Get the lines and output them. The class encapsulates the output task and makes it reusable. In this simple example that may look superfluous but think a little larger. Like output an select-field or csv.

Encapsulate file()


Still we haven't used an Iterator, but just encapsulated the output. PHP provides several default iterators and one of them is the ArrayIterator.

$output = new MyProjectLineOutput(
AAnew ArrayIterator(
AAAAarray(
AAAAAA'line one', 'line two'
AAAA)
AA)
);
$output();

The ArrayIterator just takes an array and makes it an Iterator. It is mostly used to implement another interface - IteratorAggregate. Both the "Iterator" and the "IteratorAggregate" interfaces inherit from a common ancestor named "Traversable". You can not implement "Traversable" directly but use it to validate if an object is traversable or in other words can be used with foreach.

Now let's load the lines from a file.

class MyProjectFile implements IteratorAggregate {
AAprivate $_file;

AApublic function __construct($file) {
AAAA$this-_file = $file;
AA}

AApublic function getIterator() {
AAAAreturn new ArrayIterator(file($this-_file));
AA}
}

$output = new MyProjectLineOutput(
AAnew MyProjectFile('sample.txt')
);
$output();


The main difference between using file directly to this is that the file() is accessed later in the process. The foreach() inside the MyProjectLineOutput::output() method calls MyProjectFile::getIterator(). Until then we can pass the instance of MyProjectFile around without loading the file into memory. Unlike a direct call to file() we don't pass the concrete data around but an information how it can be obtained.

Iterating A Text File


Implementing Iterator we can make sure that only a part of the file needs to be loaded.

class MyProjectFileUnbuffered implements Iterator {
AAprivate $_file;
AAprivate $_handle = NULL;
AAprivate $_key = -1;
AAprivate $_current = NULL;

AApublic function __construct($file) {
AAAA$this-_file = $file;
AA}

AApublic function __destruct() {
AAAAif (is_resource($this-_handle)) {
AAAAAAfclose($this-_handle);
AAAA}
AA}

AApublic function rewind() {
AAAAif (!is_resource($this-_handle)) {
AAAAAA$this-_handle = fopen($this-_file, 'r');
AAAA} else {
AAAAAAfseek($this-_handle, 0);

Truncated by Planet PHP, read more at the original (another 5383 bytes)