Object Iteration

PHP 5 provides a way for objects to be defined so it is possible to iterate through a list of items, with, for example a foreach statement. By default, all visible properties will be used for the iteration.

例子 19-20. Simple Object Iteration

<?php
class MyClass
{
    
public $var1 = 'value 1';
    
public $var2 = 'value 2';
    
public $var3 = 'value 3';

    
protected $protected = 'protected var';
    
private   $private   = 'private var';

    function
iterateVisible() {
       echo
"MyClass::iterateVisible:\n";
       foreach(
$this as $key => $value) {
           print
"$key => $value\n";
       }
    }
}

$class = new MyClass();

foreach(
$class as $key => $value) {
    print
"$key => $value\n";
}
echo
"\n";


$class->iterateVisible();

?>

上例将输出:

var1 => value 1
var2 => value 2
var3 => value 3

MyClass::iterateVisible:
var1 => value 1
var2 => value 2
var3 => value 3
protected => protected var
private => private var

As the output shows, the foreach iterated through all visible variables that can be accessed. To take it a step further you can implement one of PHP 5's internal interface named Iterator. This allows the object to decide what and how the object will be iterated.

例子 19-21. Object Iteration implementing Iterator

<?php
class MyIterator implements Iterator
{
    
private $var = array();

    
public function __construct($array)
    {
        if (
is_array($array)) {
            
$this->var = $array;
        }
    }

    
public function rewind() {
        echo
"rewinding\n";
        
reset($this->var);
    }

    
public function current() {
        
$var = current($this->var);
        echo
"current: $var\n";
        return
$var;
    }

    
public function key() {
        
$var = key($this->var);
        echo
"key: $var\n";
        return
$var;
    }

    
public function next() {
        
$var = next($this->var);
        echo
"next: $var\n";
        return
$var;
    }

    
public function valid() {
        
$var = $this->current() !== false;
        echo
"valid: {$var}\n";
        return
$var;
    }
}

$values = array(1,2,3);
$it = new MyIterator($values);

foreach (
$it as $a => $b) {
    print
"$a: $b\n";
}
?>

上例将输出:

rewinding
current: 1
valid: 1
current: 1
key: 0
0: 1
next: 2
current: 2
valid: 1
current: 2
key: 1
1: 2
next: 3
current: 3
valid: 1
current: 3
key: 2
2: 3
next:
current:
valid:

You can also define your class so that it doesn't have to define all the Iterator functions by simply implementing the PHP 5 IteratorAggregate interface.

例子 19-22. Object Iteration implementing IteratorAggregate

<?php
class MyCollection implements IteratorAggregate
{
    
private $items = array();
    
private $count = 0;

    
// Required definition of interface IteratorAggregate
    
public function getIterator() {
        return new
MyIterator($this->items);
    }

    
public function add($value) {
        
$this->items[$this->count++] = $value;
    }
}

$coll = new MyCollection();
$coll->add('value 1');
$coll->add('value 2');
$coll->add('value 3');

foreach (
$coll as $key => $val) {
    echo
"key/value: [$key -> $val]\n\n";
}
?>

上例将输出:

rewinding
current: value 1
valid: 1
current: value 1
key: 0
key/value: [0 -> value 1]

next: value 2
current: value 2
valid: 1
current: value 2
key: 1
key/value: [1 -> value 2]

next: value 3
current: value 3
valid: 1
current: value 3
key: 2
key/value: [2 -> value 3]

next:
current:
valid:

注: For more examples of iterators, see the SPL Extension.


add a note add a note User Contributed Notes
baldurien at bbnwn dot eu
10-Aug-2006 09:01
Beware of how works iterator in PHP if you come from Java!

In Java, iterator works like this :
<?php
interface Iterator
<O> {
 
boolean hasNext();
 
O next();
 
void remove();
}
?>
But in php, the interface is this (I kept the generics and type because it's easier to understand)

<?php
interface Iterator
<O> {
 
boolean valid();
 
mixed key();
 
O current();
 
void next();
 
void previous();
 
void rewind();
}
?>

1. valid() is more or less the equivalent of hasNext()
2. next() is not the equivalent of java next(). It returns nothing, while Java next() method return the next object, and move to next object in Collections. PHP's next() method will simply move forward.

Here is a sample with an array, first in java, then in php :

<?php
class ArrayIterator<O> implements Iterator<O> {
 
private final O[] array;
 
private int index = 0;

 
public ArrayIterator(O[] array) {
    
this.array = array;
  }
 
 
public boolean hasNext() {
   return
index < array.length;
  } 

 
public O next() {
     if ( !
hasNext())
      
throw new NoSuchElementException('at end of array');
     return array[
index++];
  }

 
public void remove() {
  
throw new UnsupportedOperationException('remove() not supported in array');
  }
}
?>

And here is the same in php (using the appropriate function) :

<?php
/**
 * Since the array is not mutable, it should use an internal
 * index over the number of elements for the previous/next
 * validation.
 */
class ArrayIterator implements Iterator {
 
private $array;
 
public function __construct($array) {
   if ( !
is_array($array))
    
throw new IllegalArgumentException('argument 0 is not an array');
  
$this->array = array;
  
$this->rewind();
  }
 
public function valid() {
   return
current($this->array) !== false;
  
// that's the bad method (should use arrays_keys, + index)
 
}
 
public function key() {
     return
key($this->array);
  }
 
public function current() {
   return
current($this->array);
  }
 
public function next() {
   if (
$this->valid())
    
throw new NoSuchElementException('at end of array');
  
next($this->array);
  }
 
public function previous()  {
  
// fails if current() = first item of array
  
previous($this->array);
  }
 
public function rewind() {
    
reset($this->array);
  }
}
?>

The difference is notable : don't expect next() to return something like in Java, instead use current(). This also means that you have to prefetch your collection to set the current() object. For instance, if you try to make a Directory iterator (like the one provided by PECL), rewind should invoke next() to set the first element and so on. (and the constructor should call rewind())

Also, another difference :

<?php
class ArrayIterable<O> implements Iterable<O> {
 
private final O[] array;

 
public ArrayIterable(O[] array) {
    
this.array = array;
  } 

 
public Iterator<O> iterator() {
     return new
ArrayIterator(array);
  }
}
?>

When using an Iterable, in Java 1.5, you may do such loops :

<?php
for ( String s : new ArrayIterable<String>(new String[] {"a", "b"})) {
  ...
}
?>
Which is the same as :

<?php
Iterator
<String> it = new ArrayIterable<String>(new String[] {"a", "b"});
while (
it.hasNext()) {
 
String s = it.next();
  ...
}
?>
While in PHP it's not the case :
<?php
foreach ( $iterator as $current ) {
  ...
}
?>
Is the same as :

<?php
for ( $iterator->rewind(); $iterator->valid(); $iterator->next()) {
 
$current = $iterator->current();
  ...
}
?>

(I think we may also use IteratorAggregate to do it like with Iterable).

Take that in mind if you come from Java.

I hope this explanation is not too long...
chad 0x40 herballure 0x2e com
05-May-2006 09:46
The example code given for valid() will break if the array contains a FALSE value. This code prints out a single "bool(true)" and exits the loop when it gets to the FALSE:

<?php
$A
= array(TRUE, FALSE, TRUE, TRUE);
while(
current($A) !== FALSE) {
 
var_dump(current($A));
 
next($A);
}
?>

Instead, the key() function should be used, since it returns NULL only at the end of the array. This code displays all four elements and then exits:

<?php
$A
= array(TRUE, FALSE, TRUE, TRUE);
while(!
is_null(key($A))) {
 
var_dump(current($A));
 
next($A);
}
?>
mortanon at gmail dot com
14-Oct-2005 11:06
Hier is an example for a CSV Iterator.

<?php
class CsvIterator implements Iterator
{
   const
ROW_SIZE = 4096;
  
/**
     * The pointer to the cvs file.
     * @var resource
     * @access private
     */
  
private $filePointer = null;
  
/**
     * The current element, which will
     * be returned on each iteration.
     * @var array
     * @access private
     */
  
private $currentElement = null;
  
/**
     * The row counter.
     * @var int
     * @access private
     */
  
private $rowCounter = null;
  
/**
     * The delimiter for the csv file.
     * @var str
     * @access private
     */
  
private $delimiter = null;

  
/**
     * This is the constructor.It try to set the delimiter
     * and open the csv file.The method throws an exception
     * on failure.
     *
     * @access public
     * @param str $file The csv file.
     * @param str $delimiter The delimiter.
     *
     * @throws Exception
     */
  
public function __construct($file, $delimiter=',')
   {
      
try {
          
$this->filePointer = fopen($file, 'r');
          
$this->delimiter = $delimiter;
       }
      
catch (Exception $e) {
          
throw new Exception('The file "'.$file.'" cannot be read.');
       }
   }

  
/**
     * This method resets the file pointer.
     *
     * @access public
     */
  
public function rewind() {
      
$this->rowCounter = 0;
      
rewind($this->filePointer);
   }

  
/**
     * This method returns the current csv row as a 2 dimensional array
     *
     * @access public
     * @return array The current csv row as a 2 dimensional array
     */
  
public function current() {
      
$this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter);
      
$this->rowCounter++;
       return
$this->currentElement;
   }

  
/**
     * This method returns the current row number.
     *
     * @access public
     * @return int The current row number
     */
  
public function key() {
       return
$this->rowCounter;
   }

  
/**
     * This method checks if the end of file is reached.
     *
     * @access public
     * @return boolean Returns true on EOF reached, false otherwise.
     */
  
public function next() {
       return !
feof($this->filePointer);
   }

  
/**
     * This method checks if the next row is a valid row.
     *
     * @access public
     * @return boolean If the next row is a valid row.
     */
  
public function valid() {
       if (!
$this->next()) {
          
fclose($this->filePointer);
           return
false;
       }
       return
true;
   }
}
?>

Usage :

<?php
$csvIterator
= new CsvIterator('/path/to/csvfile.csv');
foreach (
$csvIterator as $row => $data) {
  
// do somthing with $data
}
?>
markushe at web dot de
07-Aug-2005 02:05
Just something i noticed:
It seems, that when you are implementing the interface Iterator, yout method key() has to return a string or integer.

I was trying to return a object an got this error:
Illegal type returned from MyClass::key()
just_somedood at yahoo dot com
28-Jun-2005 03:20
To clarify on php at moechofe's post, you CAN use the SPL to overide the array operator for a class.  This, with the new features of object, and autoloading (among a buch of other things) has me completely sold on PHP5.  You can also find this information on the SPL portion of the manual, but I'll post it here as well so it isn't passed up.  The below Collection class will let you use the class as an array, while also using the foreach iterator:

<?php

class Collection implements ArrayAccess,IteratorAggregate
{
  
public $objectArray = Array();
  
//**these are the required iterator functions   
  
function offsetExists($offset)
   {         
       if(isset(
$this->objectArray[$offset]))  return TRUE;
       else return
FALSE;         
   }   
  
   function &
offsetGet($offset)
   { 
       if (
$this->offsetExists($offset))  return $this->objectArray[$offset];
       else return (
false);
   }
  
   function
offsetSet($offset, $value)
   {         
       if (
$offset$this->objectArray[$offset] = $value;
       else 
$this->objectArray[] = $value;
   }
  
   function
offsetUnset($offset)
   {
       unset (
$this->objectArray[$offset]);
   }
  
   function &
getIterator()
   {
       return new
ArrayIterator($this->objectArray);
   }
  
//**end required iterator functions

  
public function doSomething()
   {
       echo
"I'm doing something";
   }
}

?>

I LOVE the new SPL stuff in PHP!  An example of usage is below:

<?php
class Contact
{
  
protected $name = NULL;

  
public function set_name($name)
   {
      
$this->name = $name;
   }
  
  
public function get_name()
   {
       return (
$this->name);
   }
}

$bob = new Collection();
$bob->doSomething();
$bob[] = new Contact();
$bob[5] = new Contact();
$bob[0]->set_name("Superman");
$bob[5]->set_name("a name of a guy");

foreach (
$bob as $aContact)
{
     echo
$aContact->get_name() . "\r\n";
}
?>

Would work just fine.  This makes code so much simpler and easy to follow, it's great.  This is exactly the direction I had hoped PHP5 was going!
PrzemekG_ at poczta dot onet dot pl
27-May-2005 07:20
If you want to do someting like this:
<?php
foreach($MyObject as $key => &$value)
  
$value = 'new '.$value;
?>
you must return values by reference in your iterator object:
<?php
class MyObject implements Iterator
{
/* ...... other iterator functions ...... */
/* return by reference */
public function &current()
{
   return
$something;
}
?>

This won't change values:
<?php
foreach($MyObject as $key => $value)
  
$value = 'new '.$value;
?>

This will change values:
<?php
foreach($MyObject as $key => &$value)
  
$value = 'new '.$value;
?>

I think this should be written somewhere in the documentations, but I couldn't find it.
elias at need dot spam
11-Apr-2005 06:15
The MyIterator::valid() method above ist bad, because it
breaks on entries with 0 or empty strings, use key() instead:

<?php
public
function valid()
{
   return !
is_null(key($this->var));
}
?>

read about current() drawbacks:
http://php.net/current
strrev('ed.relpmeur@ekneos');
02-Mar-2005 06:25
Use the SPL ArrayAccess interface to call an object as array:

http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html
phpnet at nicecupofteaandasitdown dot com
23-Feb-2005 12:09
You should be prepared for your iterator's current method to be called before its next method is ever called. This certainly happens in a foreach loop. If your means of finding the next item is expensive you might want to use something like this

private $item;
      
function next() {
   $this->item = &$this->getNextItem();
   return $this->item;
}
  
public function current() {
     if(!isset($this->item)) $this->next();
   return $this->item;
}
knj at aider dot dk
19-Dec-2004 11:19
if you in a string define classes that implements IteratorAggregate.
you cant use the default;
<?
...
public function getIterator() {
       return new
MyIterator($this-><What ever>);
}
..
?>
at least not if you want to use eval(<The string>).
You have to use:
<?
...
public function getIterator() {
    
$arrayObj=new ArrayObject($this-><What ever>);
     return
$arrayObj->getIterator();
}
...
?>
php at moechofe dot com
13-Dec-2004 07:27
<?php

class MyIterator implements Iterator {

 
private $var = array();

 
public function __construct() {
  
$this->var = array( 1,2,3,4 ); }

 
public function rewind() { reset($this->var); }
 
public function current() { return current($this->var); }
 
public function key() { return key($this->var); }
 
public function next() { return next($this->var); }
 
public function valid() { return $this->current() !== false; }

  }

$it = new MyIterator();

// dont work :(
echo $it[0];

?>