Regarding iterators, we have already talked about what iterators are in previous articles related to design patterns, and we have also used SPL examples to demonstrate them. If you haven't read the previous articles, you can go back and take a look! PHP design pattern iterator mode : https://mp.weixin.qq.com/s/uycac0OXYYjAG1BlzTUjsw .

Therefore, for the concept of iterators, we won't say much here. The main content of today is to understand which iterators are included in the SPL extension and their functional effects. In addition, the array iterator ArrayIterator that we touched in the previous article will not be discussed here because we have already learned it. In addition, there are file directory-related iterators, which will also be explained in articles related to file directory operations, including the iterators learned below, and many of them have corresponding recursive iterators, such as our The CachingIterator, FilterIterator, etc. to be mentioned below all have their corresponding RecursiveCachingIterator and RecursiveFilterIterator classes. Let's study it by yourself. With recursive iterators, there are two more methods getChildren() and hasChildren(). , And finally we will implement our own iterator class, which will talk about recursion.

IteratorIterator wraps iterator

First, let's take a look at what is a wrapped iterator. It is also an iterator, but when it is instantiated, an iterator must be passed in and stored internally, which is an internal iterator InnerIterator. For its own iterator interface functions, they are actually the operation functions related to the internal iterator that are forwarded and called. It actually feels a bit like a decorator pattern. We can upgrade the original iterator function by inheriting IteratorIterator.

$iterator = new IteratorIterator(new ArrayIterator([1, 2, 3]));
$iterator->rewind();
while ($iterator->valid()) {
    echo $iterator->key(), ": ", $iterator->current(), PHP_EOL;
    $iterator->next();
}
// 0: 1
// 1: 2
// 2: 3

It can be seen from the code that its construction parameter must be an iterator, and the parameter signature itself needs an object that implements the Traversable interface. The Traversable interface is the interface that all iterators must implement.

class OutIterator extends IteratorIterator
{
    public function rewind()
    {
        echo __METHOD__, PHP_EOL;
        return parent::rewind();
    }

    public function valid()
    {
        echo __METHOD__, PHP_EOL;
        return parent::valid();
    }

    public function current()
    {
        echo __METHOD__, PHP_EOL;
        return parent::current() . '_suffix';
    }

    public function key()
    {
        echo __METHOD__, PHP_EOL;
        return parent::key();
    }

    public function next()
    {
        echo __METHOD__, PHP_EOL;
        return parent::next();
    }

    public function getInnerIterator()
    {
        echo __METHOD__, PHP_EOL;
        return parent::getInnerIterator();
    }
}
$iterator = new OutIterator(new ArrayIterator([1, 2, 3]));
foreach ($iterator as $k => $v) {
    echo $k, ': ', $v, PHP_EOL;
}
// OutIterator::rewind
// OutIterator::valid
// OutIterator::current
// OutIterator::key
// 0: 1_suffix
// OutIterator::next
// OutIterator::valid
// OutIterator::current
// OutIterator::key
// 1: 2_suffix
// OutIterator::next
// OutIterator::valid
// OutIterator::current
// OutIterator::key
// 2: 3_suffix
// OutIterator::next
// OutIterator::valid

We wrote an OutIterator class ourselves and inherited from IteratorIterator class, and then rewrite all iterator-related methods and functions. In these functions, add some output debugging information, and finally traverse the iterator through foreach. It can be seen that after foreach judges whether the object is iterable, it will call the corresponding iterator method function like we use while to traverse the iterator. This example is quite intuitive, and it is also very helpful for us to understand what the iterators are doing.

var_dump($iterator->getInnerIterator());
// object(ArrayIterator)#5 (1) {
//     ["storage":"ArrayIterator":private]=>
//     array(3) {
//       [0]=>
//       int(1)
//       [1]=>
//       int(2)
//       [2]=>
//       int(3)
//     }
//   }

Through the getInerIterator() method, we can get the iterator object inside the package iterator. Here you can clearly see the information about the iterator we placed inside it.

Next, we will learn some iterators derived from the IteratorIterator class. In other words, they are all inherited from IteratorIterator, a wrapper iterator, and add a lot of unique features on top of it.

AppendIterator Append Iterator

Append iterator, a very strange name, let's take a look at what it does.

$appendIterator = new AppendIterator();
$appendIterator->append(new ArrayIterator([1, 2, 3]));
$appendIterator->append(new ArrayIterator(['a' => 'a1', 'b' => 'b1', 'c' => 'c1']));
var_dump($appendIterator->getIteratorIndex()); // int(0)
foreach ($appendIterator as $k => $v) {
    echo $k, ': ', $v, PHP_EOL;
    echo 'iterator index: ', $appendIterator->getIteratorIndex(), PHP_EOL;
}
// 0: 1
// iterator index: 0
// 1: 2
// iterator index: 0
// 2: 3
// iterator index: 0
// a: a1
// iterator index: 1
// b: b1
// iterator index: 1
// c: c1
// iterator index: 1

var_dump($appendIterator->getIteratorIndex()); // NULL

Yes, you read that right, the function of this append iterator is to store multiple internal iterators in it. We can continue to add through the append() method, and through getIteratorIndex() we can see which internal iterator is currently being used or traversed.

If you want to get the internal iterator object, although there are also getInnerIterator() methods inherited from IteratorIterator, it is better to use another method.

var_dump($appendIterator->getArrayIterator());
// object(ArrayIterator)#2 (1) {
//     ["storage":"ArrayIterator":private]=>
//     array(2) {
//       [0]=>
//       object(ArrayIterator)#7 (1) {
//         ["storage":"ArrayIterator":private]=>
//         array(3) {
//           [0]=>
//           int(1)
//           [1]=>
//           int(2)
//           [2]=>
//           int(3)
//         }
//       }
//       [1]=>
//       object(ArrayIterator)#9 (1) {
//         ["storage":"ArrayIterator":private]=>
//         array(3) {
//           ["a"]=>
//           string(2) "a1"
//           ["b"]=>
//           string(2) "b1"
//           ["c"]=>
//           string(2) "c1"
//         }
//       }
//     }
//   }

getArrayIterator() can return all internal iterators in an array form.

CachingIterator

As you can see from the English name, cache iterators.

$cachingIterator = new CachingIterator(new ArrayIterator([1, 2, 3]), CachingIterator::FULL_CACHE);
var_dump($cachingIterator->getCache());
// array(0) {
// }
foreach ($cachingIterator as $c) {

}
var_dump($cachingIterator->getCache());
// array(3) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//   }

Its more distinctive feature is the getCache() method. Do you see any problems from the test code above? That's right, when we traverse the iterator once, the data information of the internal iterator will be cached in the array returned by the getCache() method. We call the getCache() method before the traversal without any content. In addition, by constructing the second parameter of the parameter, we can specify the information content of the cached data. Here we use CachingIterator::FULL_CACHE, which is to cache the entire content.

FilterIterator filter iterator

Are you familiar with the word filtering? The function array_filter() also performs filtering operations on arrays. Similarly, the FilterIterator iterator achieves a similar effect. But before learning to use this FilterIterator, let's learn about its two derived classes.

$callbackFilterIterator = new CallbackFilterIterator(new ArrayIterator([1, 2, 3, 4]), function ($current, $key, $iterator) {
    echo $key, ': ', $current, PHP_EOL;
    if ($key == 0) {
        var_dump($iterator);
    }
    if ($current % 2 == 0) {
        return true;
    }
    return false;
});
foreach ($callbackFilterIterator as $c) {
    echo 'foreach: ', $c, PHP_EOL;
}
// 0: 1
// object(ArrayIterator)#13 (1) {
//   ["storage":"ArrayIterator":private]=>
//   array(4) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//     [3]=>
//     int(4)
//   }
// }
// 1: 2
// foreach: 2
// 2: 3
// 3: 4
// foreach: 4

The CallbackFilterIterator is an iterator for filtering operations through the callback function specified in the second parameter of the construction parameter. If you want the data to pass, return true, otherwise return false. Let me talk about this iterator first because it is so similar to array_filter(). Array_filter() also uses a callback function to perform filtering judgments.

$regexIterator = new RegexIterator(new ArrayIterator(['test1', 'test2', 'opp1', 'test3']), '/^(test)(\d+)/', RegexIterator::MATCH);

var_dump(iterator_to_array($regexIterator));
// array(3) {
//     [0]=>
//     string(5) "test1"
//     [1]=>
//     string(5) "test2"
//     [3]=>
//     string(5) "test3"
//   }

$regexIterator = new RegexIterator(new ArrayIterator(['test1', 'test2', 'opp1', 'test3']), '/^(test)(\d+)/', RegexIterator::REPLACE);
$regexIterator->replacement = 'new $2$1'; 
var_dump(iterator_to_array($regexIterator));
// array(3) {
//     [0]=>
//     string(9) "new 1test"
//     [1]=>
//     string(9) "new 2test"
//     [3]=>
//     string(9) "new 3test"
//   }

RegexIterator believes that there is no need to explain it. It uses regular expressions to filter and judge. It should be noted here that we use an iterator_to_array() function, which is also a function in SPL. Its role is to convert the iterator to an array. In fact, it solves the trouble of writing a foreach or while loop to demonstrate.

Through the study of the above two FilterIterator derived classes, I believe everyone is more interested in this filter iterator. However, this original FilterIterator is an abstract class, that is, it cannot be instantiated directly. We can only write another class to inherit it and implement one of its core methods accept().

class MyFilterIterator extends FilterIterator{
    public function accept(){
        echo  __METHOD__, PHP_EOL;
        if($this->current()%2==0){
            return true;
        }
        return false;
    }
}
$myFilterIterator = new MyFilterIterator(new ArrayIterator([1,2,3,4]));
var_dump(iterator_to_array($myFilterIterator));
// MyFilterIterator::accept
// MyFilterIterator::accept
// MyFilterIterator::accept
// MyFilterIterator::accept
// array(2) {
//   [1]=>
//   int(2)
//   [3]=>
//   int(4)
// }

Many friends must have understood that whether it is the above CallbackFilterIterator or RegexIterator, they are an implementation class that implements FilterIterator, and they all override the accept() method. They pass the required data through the constructor. In the process of core use, CallbackFilterIterator calls the passed callback method in accept(), while RegexIterator performs data on the internal iterator in accept() The judgment of regular expressions.

InfiniteIterator

Infinite iterator? What the hell, it looks very tall. This is a pit, be careful.

$infinateIterator = new InfiniteIterator(new ArrayIterator([1,2,3,4]));
$i = 20;
foreach($infinateIterator as $k=>$v){
    echo $k, ': ', $v, PHP_EOL;
    $i--;
    if($i <= 0){
        break;
    }
}
// 0: 1
// 1: 2
// 2: 3
// 3: 4
// 0: 1
// 1: 2
// 2: 3
// ………………
// ………………

To put it bluntly, it is similar to realize the function of pointing the pointer back to the first data when next() reaches the last data. It feels a bit like a circular queue, that is to say, if we traverse this infinite iterator without restrictions, it will become an endless loop and keep looping.

LimitIterator number limit iterator

Just look at the name. Just like the page turning function we often operate on MySQL databases, LimitIterator returns part of the data based on the start and offset range values.

$limitIterator = new LimitIterator(new ArrayIterator([1,2,3,4]),0,2);
var_dump(iterator_to_array($limitIterator));
// array(2) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//   }

$limitIterator = new LimitIterator(new ArrayIterator([1,2,3,4]),1,3);
var_dump(iterator_to_array($limitIterator));
// array(3) {
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//     [3]=>
//     int(4)
//   }

NoRewindIterator

The last iterator in the IteratorIterator series to be introduced is this NoRewindIterator. Similarly, we can see some clues from the name, that is, this iterator does not have a rewind() method, or that this method does not work.

$noRewindIterator = new NoRewindIterator(new ArrayIterator([1,2,3,4]));
var_dump(iterator_to_array($noRewindIterator));
// array(4) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//     [3]=>
//     int(4)
//   }
$noRewindIterator->rewind();
var_dump(iterator_to_array($noRewindIterator));
// array(0) {
// }

We saw earlier that in foreach(), the rewind() method is called at the beginning of each traversal to bring the data pointer back to the top. Similarly, the iterator_to_array() method has similar steps in its internal implementation. But if it is NoRewindIterator, there will be no content in the second traversal, because its rewind() method is not effective, or it is an empty method.

You can try to test it with a while() loop, which is clearer than using iterator_to_array().

MultipleIterator Multiple parallel iterators

After stepping out of IteratorIterator, let's look at an iterator that has nothing to do with it, that is, this iterator does not inherit or use IteratorIterator related methods and functions.

From the name, Multiple means multiple. Is it because there are multiple iterators inside? This is not the same as AppendIterator. Well, I admit that it does store some iterators internally, but note that these are not built-in iterators, and are different from IteratorIterator. In addition, its manifestation is also different from AppendIterator.

$multipleIterator = new MultipleIterator();
$multipleIterator->attachIterator(new ArrayIterator([1,2,3,4]));
$multipleIterator->attachIterator(new ArrayIterator(['a' => 'a1', 'b' => 'b1', 'c' => 'c1']));
$arr1 = new ArrayIterator(['a', 'b', 'c']);
$arr2 = new ArrayIterator(['d', 'e', 'f', 'g', 'h']);
$multipleIterator->attachIterator($arr1);
$multipleIterator->attachIterator($arr2);

var_dump($multipleIterator->containsIterator($arr1)); // bool(true)
$multipleIterator->detachIterator($arr1);
var_dump($multipleIterator->containsIterator($arr1)); // bool(false)

// iterator_to_array($multipleIterator);
foreach($multipleIterator as $k=>$v){
    var_dump($k);
    var_dump($v);
}
// array(3) {
//     [0]=>
//     int(0)
//     [1]=>
//     string(1) "a"
//     [2]=>
//     int(0)
//   }
//   array(3) {
//     [0]=>
//     int(1)
//     [1]=>
//     string(2) "a1"
//     [2]=>
//     string(1) "a"
//   }
//   array(3) {
//     [0]=>
//     int(1)
//     [1]=>
//     string(1) "b"
//     [2]=>
//     int(1)
//   }
//   array(3) {
//     [0]=>
//     int(2)
//     [1]=>
//     string(2) "b1"
//     [2]=>
//     string(1) "b"
//   }
//   array(3) {
//     [0]=>
//     int(2)
//     [1]=>
//     string(1) "c"
//     [2]=>
//     int(2)
//   }
//   array(3) {
//     [0]=>
//     int(3)
//     [1]=>
//     string(2) "c1"
//     [2]=>
//     string(1) "e"
//   }

We can add iterators through attachIterator(), determine whether the specified iterator exists through containsIterator(), or delete an iterator through detachIterator(). But the main feature is the result of traversal.

Regardless of key() or current(), the returned data is an array. In fact, this array is the content corresponding to each iterator. For example, the first key() returns the position of subscript 0 of the first iterator, the second iterator subscript a and the third iterator subscript. 0 position. In other words, it returns the index information of the first position of all iterators at once. In the same way, current() returns all the data information at the current position.

In addition, we can see that the amount of internal data of different iterators is different. MultipleIterator will only return the least amount of data. You can try this yourself.

Implement an iterator class yourself

Speaking of so many iterators, should we simply implement an iterator that allows count() to take effect and has a recursive implementation function that can set cursors.

class NewIterator implements Countable, RecursiveIterator, SeekableIterator {
    private $array = [];

    public function __construct($arr = []){
        $this->array = $arr;
    }

    // Countable
    public function count(){
        return count($this->array);
    }

    // RecursiveIterator
    public function hasChildren(){
        if(is_array($this->current())){
            return true;
        }
        return false;
    }

    // RecursiveIterator
    public function getChildren(){
        
        if(is_array($this->current())){
            return new ArrayIterator($this->current());
        }
        return null;
    }

    // Seekable
    public function seek($position) {
        if (!isset($this->array[$position])) {
            throw new OutOfBoundsException("invalid seek position ($position)");
        }

        $this->position = $position;
    }
      
      public function rewind() {
          $this->position = 0;
      }
  
      public function current() {
          return $this->array[$this->position];
      }
  
      public function key() {
          return $this->position;
      }
  
      public function next() {
          ++$this->position;
      }
  
      public function valid() {
          return isset($this->array[$this->position]);
      }
}

$newIterator = new NewIterator([1,2,3,4, [5,6,7]]);
var_dump(iterator_to_array($newIterator));
// array(5) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//     [3]=>
//     int(4)
//     [4]=>
//     array(3) {
//       [0]=>
//       int(5)
//       [1]=>
//       int(6)
//       [2]=>
//       int(7)
//     }
//   }

var_dump(count($newIterator));
// int(5)

$newIterator->rewind();
while($newIterator->valid()){
    if($newIterator->hasChildren()){
        var_dump($newIterator->getChildren());
    }
    $newIterator->next();
}
// object(ArrayIterator)#37 (1) {
//     ["storage":"ArrayIterator":private]=>
//     array(3) {
//       [0]=>
//       int(5)
//       [1]=>
//       int(6)
//       [2]=>
//       int(7)
//     }
//   }

$newIterator->seek(2);
while($newIterator->valid()){
    var_dump($newIterator->current());
    $newIterator->next();
}
// int(3)
// int(4)
// array(3) {
//   [0]=>
//   int(5)
//   [1]=>
//   int(6)
//   [2]=>
//   int(7)
// }

There is not much explanation about the code, and there are instructions in the comments. The most important thing is to implement the three interfaces of Countable, RecursiveIterator, and SeekableIterator. They correspond to count ability, recursion ability, and cursor setting ability respectively.

Summarize

There are a lot of things, the implementation of various iterators can be said to be a very important content in SPL. In addition to these introduced today, there are other iterators that we will explain independently in related articles. Today's content alone is estimated to be difficult to digest, hurry up and absorb it, the drag racing is still going on!

Test code:

https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/01/source/5. PHP's SPL extension library (3) iterator.php

Reference documents:

https://www.php.net/manual/zh/spl.iterators.php


硬核项目经理
90 声望18 粉丝