• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 遍历对象

    PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用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
    

    如上所示,foreach遍历了所有其能够访问的可见属性。

    更进一步,可以实现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方法。IteratorAggregate只需要实现一个方法IteratorAggregate::getIterator(),其应返回一个实现了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 扩展。

    Note:

    PHP 5.5 及以后版本的用户也可参考生成器,其提供了另一方法来定义 Iterators。

    there is still an open bug about using current() etc. with iterators
    https://bugs.php.net/bug.php?id=49369
    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.
    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);
    }
    ?>
    
    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
    Use the SPL ArrayAccess interface to call an object as array:
    http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html
    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.
    Iterator interface usign key() next() rewind() is MORE slow than extends ArrayIterator with ArrayIterator::next(), ArrayIterator::rewind(), etc.,
    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()
    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
    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!
    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...
    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
    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();
    }
    ...
    ?>
    
    I've created a dinamic version of grzeniufication code to allow un-, serialize more than one property:
    <?php
    class Person implements \Serializable {
      public $id;
      public $name;
      public $birthDate;
      public $surname;
      public function serialize() {
        return serialize((array) $this);
      }
      public function unserialize($serialized): void {
        foreach (unserialize($serialized) as $p => $v) {
          $this->{$p} = $v;
        }
      }
    }
    szerintetek ez normális, hogy objektumon végigiterálhatsz???
    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;
    }
    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.
    Please note that if you implement your iterator this way instead of with an IteratorAggregate you can not nest foreach-loops. This is because when the inner-loop is done the cursor is beyond the last element, then the outer-loop asks for the next element and finds the cursor beyond the last element as the innter-loop left it there.
    <?php
     // Wont work!
    foreach($collection as $a) {
     foreach($collection as $b) {
      var_dump($a->someFunc($b));
     }
    }
    ?>
    

    上篇:重载

    下篇:魔术方法