SPL Iterators

Why?

  • Object oriented style (objects are our friends)
  • Readability
  • Less CCN in our classes
  • Help to maintain the single responsibility principle
  • Easier unit tests (Filter classes)
  • Performance, sometimes (avoid extra queries, API calls...)
  • Iterator word is cooler than array

When?

Not always

The iterator implies overhead if the collection is very small

And, use iterators is not a rule, they can add unnecessary complexity

Yes yes, but when?

How many times did you see methods like prepareXXX() ?

private function prepareArrayToInsertInBd(array $rows) {

    $prepared_rows = array();

    foreach($rows as $row) {
        
        $prepared_row = array();

        $prepared_row['id_row'] = $row['id'];
        $prepared_row['title'] = trim($row['post_title']);
        
        /* more stuff */

        $prepared_rows[] = $prepared_row;
    }

    return $prepared_rows;
}

Should I replace all methods like the previous one?

  • Is the method calling to another auxiliar method?
  • The "prepare" code can be used in another site?

Lazy load data

public function getPostsData() {
    /* ... some stuff */
    $authors_cached_data = array();

    foreach ($posts_data as &$post) {
        $author_id = $post['author_id']

        // Avoid request already requested author data.
        if (!isset($authors_cached_data[$author_id])) {
            $authors_cached_data[$author_id] = ApiCaller::getData( ... );
        }

        $post['author_data'] = $authors_cached_data[$author_id];
    }

    return $posts_data;
}

What if the method caller does not traverse the entire collection?

 

We just made unnecessary api calls...

Avoid the early creation of large objects

We can have performance problems if we create a large collection of objects...
Typical RowsetIterator creates the entity object only when is requested.

Reducing CCN

$array = array(
	array('pear', 'apple'),
	array('banana', 'coconut'),
	'orange' // This is not an array element
);


foreach ($array as $value) {	
    // Check type of element...
    if (is_array($value)) {
        foreach ($value as $fruit) {
            echo $fruit . PHP_EOL;
        }
    } else {
        echo $value . PHP_EOL;
    }
}

/* OUTPUT:
pear
apple
banana
coconut
orange
*/

Next code do the same

$iterator = new RecursiveArrayIterator($array);

foreach(new RecursiveIteratorIterator($iterator) as $value) {
    echo $value . PHP_EOL;
}

RecursiveIteratorIterator is a decorator that does the check if the element is an array for us.

 

But, be careful with objects

$object = new stdClass();
$object->property1 = 'property 1';
$object->property2 = 'property 2';

$array = array(
    array('pear', 'apple'),
    array('banana', 'coconut'),
    'orange', // This is not an array element
    $object
);

$iterator = new RecursiveArrayIterator($array);

foreach(new RecursiveIteratorIterator($iterator) as $value) {
    echo $value . PHP_EOL;
}

/* OUTPUT: http://3v4l.org/RNKd1
pear
apple
banana
coconut
orange
property 1
property 2
*/

PHP can iterate the public properties of the objects by default

Avoid confusing method declarations

 

/**
 *
 * @include_author_data boolean If true, the posts data will 
 *                              include the author data.
 * @return array
 */
public function getPostsData($include_author_data = true) {
    /* ... some stuff */
    if ($include_author_data) {
        foreach ($posts_data as &$post) {
            $author_id = $post['author_id']
            $post['author_data'] = ApiCaller::getData( ... );
        }
    }

    return $posts_data;
}

Example:


$object->getPostsData(false);

//WTF is false? you have to enter to the method to know...

We can do:

PostsDataIterator implements IteratorAggregate {

    protected $collection;

    public function __construct() {
        $collection = $this->getPostsData();
    }

    protected function getPostsData() {
        /* query code ... etc */
        return $posts;
    }

    public function getInnerIterator() {
        return new ArrayIterator($this->collection);
    }
}

PostsDataWithAuthor extends PostsDataIterator {
    
    protected function getPostsData() {
        foreach (parent::getPostsData() as &$post) {
            /* get author data... */
        }
    
        return $posts;
    }
}

Less memory leaks

When you pass an array between methods, PHP will make a copy.

If you pass objects, PHP will pass refererences.

With large arrays, this may be a problem if the array is passed for many methods.

SPL Exceptions instead of warnings

$fruits = new ArrayIterator(array(
    'apple',
    'banana',
    'cherry',
    'orange',
    'pear'
));

// Request the elements from a non existing position
foreach (new LimitIterator($fruits, 10, 5) as $fruit) {
    var_dump($fruit);
}

/* OUTPUT:
Fatal error: Uncaught exception 'OutOfBoundsException' 
with message 'Seek position 10 is out of range' in /in/lDOgh:13
*/

Some examples

FilterIterator

Provides a simply interface to implements complex filter rules

abstract FilterIterator extends IteratorIterator implements OuterIterator {
/* Métodos */
public abstract bool accept ( void )
public __construct ( Iterator $iterator )
public mixed current ( void )
public Iterator getInnerIterator ( void )
public mixed key ( void )
public void next ( void )
public void rewind ( void )
public bool valid ( void )
}

DirectoryIterator

<?php

// avoid the use of the PHP native functions in procedural style.

foreach (new DirectoryIterator('/secret_folder/porn') as $fileInfo) {
    if($fileInfo->isDot()) continue;
    echo $fileInfo->getFilename() . "<br>\n";
}

GlobIterator 

// similar to glob PHP function

foreach (new \GlobIterator("*.php") as $php_file) {
    echo "$php_file\n";
}

AppendIterator

<?php

$iterator1 = new ArrayIterator(array(1,2));
$iterator2 = new ArrayIterator(array(3,4));

$appendIterator = new AppendIterator();
$appendIterator->append($iterator1);
$appendIterator->append($iterator2);

foreach($appendIterator as $element) {
	echo $element . PHP_EOL;
}

/*OUTPUT
1
2
3
4
*/

Using nested iterators

<?php

$fruits = new ArrayIterator(array(
    'apple',
    'banana',
    'cherry',
    'orange',
    'pear'
));

// Passing by the custom iterator only a substet of the elements
foreach (new MyCustomIterator(new LimitIterator($fruits , 2, 2)) as $fruit) {
    var_dump($fruit);
}

OutOfBoundsException

(END)

SPL Iterators

By Odín del Río Piñeiro

SPL Iterators

Presentation about the PHP SPL Iteratos

  • 648