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";
}
// 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