类与对象
在线手册:中文 英文
PHP手册

对象迭代

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. PHP5提供了一种迭代(iteration)对象的功能,就像使用数组那样,可以通过foreach 来遍历对象中的属性。默认情况下,在外部迭代只能得到外部可见的属性的值。

Example #1 简单的对象迭代

<?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

如上所示, the foreach 遍历了所有 可见的 属性. 你也可以通过实现PHP 5自带的 Iterator接口来实现迭代. 使用Iterator接口可以让对象自行决定如何迭代自已。

Example #2 实现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:

你也可以让类实现IteratorAggregate接口,这样你的类就不用强制性地实现 Iterator接口中的所有方法。

Example #3 通过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:

Note:

请查看SPL Extension了解更多Iterator相关信息。


类与对象
在线手册:中文 英文
PHP手册
PHP手册 - N: 对象迭代

用户评论:

uramihsayibok, gmail, com (07-Feb-2010 04:23)

With method names like "current", "key", and "next", one might think that you can use the corresponding functions on your objects. For instance,

<?php

class MyIterator implements Iterator {
   
// as defined in example #2
}

$iterator = new MyIterator(array(1, 2, 3));
reset($iterator); // calls $iterator->rewind?
var_dump(current($iterator)); // calls $iterator->current()?

?>

This doesn't work. It has been reported as a bug (and honestly it *would* make sense) but was rejected - objects can be treated as arrays... and that's what happens.

Peter &#39;the Pete&#39; de Pijd (01-Jul-2009 06:12)

This is what seems to work for checking isFirst() and isLast() on a class implementing the Iterator interface - without destroying the internal array pointer:

<?php
// Let $this->_elements be your internal array with
// array("one", "two", "three")
public function isFirst()
{
   
$hasPrevious = prev($this->_elements);
   
// now undo
   
if ($hasPrevious) {
       
next($this->_elements);
    } else {
       
reset($this->_elements);
    }
    return !
$hasPrevious;
}

public function
isLast()
{
   
$hasNext = next($this->_elements);
   
// now undo
   
if ($hasNext) {
       
prev($this->_elements);
    } else {
       
end($this->_elements);
    }
    return !
$hasNext;
}

// usage
foreach ($myInterator as $value) {
  echo
$value;
  if (
$myInterator->isFirst()) {
    echo
" (first)";
  }
  if (
$myInterator->isLast()) {
    echo
" (last)";
  }
  echo
" - value is still the same: ", $value, "<br />";
}

?>

output:
one (first) - value is still the same: one
two - value is still the same: two
three (last) - value is still the same: three

This can be helpfull for designing CSS elements where the first or last element must have a different padding/margin than the others.

hlegius at gmail dot com (15-Oct-2008 01:06)

Iterator interface usign key() next() rewind() is MORE slow than extends ArrayIterator with ArrayIterator::next(), ArrayIterator::rewind(), etc.,

wavetrex A(nospam)T gmail DOT com (01-Mar-2008 10:50)

By reading the posts below I wondered if it really is impossible to make an ArrayAccess implementation really behave like a true array ( by being multi level )

Seems like it's not impossible. Not very preety but usable

<?php

class ArrayAccessImpl implements ArrayAccess {

  private
$data = array();

  public function
offsetUnset($index) {}

  public function
offsetSet($index, $value) {
//    echo ("SET: ".$index."<br>");
   
   
if(isset($data[$index])) {
        unset(
$data[$index]);
    }
   
   
$u = &$this->data[$index];
    if(
is_array($value)) {
       
$u = new ArrayAccessImpl();
        foreach(
$value as $idx=>$e)
           
$u[$idx]=$e;
    } else
       
$u=$value;
  }

  public function
offsetGet($index) {
//    echo ("GET: ".$index."<br>");

   
if(!isset($this->data[$index]))
       
$this->data[$index]=new ArrayAccessImpl();
   
    return
$this->data[$index];
  }

  public function
offsetExists($index) {
//    echo ("EXISTS: ".$index."<br>");
   
   
if(isset($this->data[$index])) {
        if(
$this->data[$index] instanceof ArrayAccessImpl) {
            if(
count($this->data[$index]->data)>0)
                return
true;
            else
                return
false;
        } else
            return
true;
    } else
        return
false;
  }

}

echo
"ArrayAccess implementation that behaves like a multi-level array<hr />";

$data = new ArrayAccessImpl();

$data['string']="Just a simple string";
$data['number']=33;
$data['array']['another_string']="Alpha";
$data['array']['some_object']=new stdClass();
$data['array']['another_array']['x']['y']="LOL @ Whoever said it can't be done !";
$data['blank_array']=array();

echo
"'array' Isset? "; print_r(isset($data['array'])); echo "<hr />";
echo
"<pre>"; print_r($data['array']['non_existent']); echo "</pre>If attempting to read an offset that doesn't exist it returns a blank object! Use isset() to check if it exists!<br>";
echo
"'non_existent' Isset? "; print_r(isset($data['array']['non_existent'])); echo "<br />";
echo
"<pre>"; print_r($data['blank_array']); echo "</pre>A blank array unfortunately returns similar results :(<br />";
echo
"'blank_array' Isset? "; print_r(isset($data['blank_array'])); echo "<hr />";
echo
"<pre>"; print_r($data); echo "</pre> (non_existent remains in the structure. If someone can help to solve this I'll appreciate it)<hr />";

echo
"Display some value that exists: ".$data['array']['another_string'];

?>

(in the two links mentioned below by artur at jedlinski... they say you can't use references, so I didn't used them.
My implementation uses recursive objects)

If anyone finds a better (cleaner) sollution, please e-mail me.
Thanks,
Wave.

doctorrock83_at_gmail.com (18-May-2007 11:10)

Please remember that actually the only PHP iterating structure that uses Iterator is foreach().

Any each() or list() applied to an Object implementing iterator will not provide the expected result

artur at jedlinski dot pl (22-Apr-2007 08:38)

One should be aware that ArrayAccess functionality described by "just_somedood at yahoo dot com" below is currently broken and thus it's pretty unusable.

Read following links to find more:
http://bugs.php.net/bug.php?id=34783
http://bugs.php.net/bug.php?id=32983

rune at zedeler dot dk (28-Feb-2007 04:00)

The iterator template from knj at aider dot dk does not yield correct results.
If you do
<?
reset($a);
next($a);
echo current($a);
?>
where $a is defined over the suggested template, then the first element will be output, not the second, as expected.

baldurien at bbnwn dot eu (10-Aug-2006 02: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 02: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);
}
?>

markushe at web dot de (06-Aug-2005 07: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 (27-Jun-2005 08: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 12: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 (10-Apr-2005 11: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'); (01-Mar-2005 10: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 (22-Feb-2005 04: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 03: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();
}
...
?>