PHP SPL extension library (III) iterator

Time:2021-11-24

As for iterators, we have 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 have a look!Iterator pattern of PHP design patternhttps://mp.weixin.qq.com/s/uycac0OXYYjAG1BlzTUjsw

Therefore, we won’t talk more about the concept of iterators here. Today’s main content is to understand which iterators are included in SPL extensions and their functions and effects. In addition, the array iterator that we came into contact with in the last article will not be discussed here because we have already learned it. In addition, there are iterators related to files and directories, which will also be explained in the articles related to file and directory operations, including these iterators learned below. Many of them have corresponding recursive iterators, such as cachengitters, filteriteters, etc., which we will talk about below Recursivefilteritrator class. Let’s study it by ourselves. With recursive iterators, there are only two more methods getchildren () and haschildren (). Finally, we will implement our own iterator class, which will talk about recursion.

Iterator iterator wrapper iterator

First, let’s look at what a wrapper iterator is. It is also an iterator, but when instantiating, it must pass in an iterator and save it internally. It is an internal iterator inneriterator. For its own iterator interface functions, they are actually the operation functions related to the internal iterator that forwards the call. It actually feels like a decorator pattern. We can upgrade the original iterator function by inheriting iterator iterator.

$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 parameters must also be an iterator, and its own parameter signature requires an object that implements the traversable interface. The traversable interface is an 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, inherited it from the iterator iterator class, and then overridden 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 the foreach judges whether the object can be iterated, it will call the corresponding iterator method function as we use while to traverse the iterator. This example is quite intuitive and is very helpful for us to understand what the stack of method functions of 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 wrapper iterator. Here you can clearly see the information about the iterator we put inside it.

Next, we’ll learn about some iterators derived from the iterator iterator class. In other words, they all inherit from iterator iterator, which is a wrapper iterator, and add many unique functions to it.

Appenditerator append iterator

Append iterator, a strange name. Let’s see what it does first.

$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 are right. The function of this additional iterator is to save multiple internal iterators in it. We can add it continuously through the append () method. We can see which internal iterator we are currently using or traversing through getiteratorindex ().

If you want to get the internal iterator object, although there is also a getinneriterator () method 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"
//         }
//       }
//     }
//   }

Getarrayitterer () can return all internal iterators as an array collection.

Caching iterator

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

$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 distinctive feature is the getcache () method. Do you see any problems from the above test code? Yes, after we traverse the iterator once, the data information of the internal iterator will be cached in the array returned by the getcache () method. The way we call getCache () before traversing is nothing. In addition, by constructing the second parameter of the parameter, we can specify the information content of the cached data. Here, we use cacheingiterator:: full_ Cache, that is, cache all the contents.

Filteriterator filter iterator

Is the word filter familiar, array_ The filter () function is also used to filter the array. Similarly, the filteritrator iterator achieves a similar effect. However, before we learn to use this filteritrator, let’s learn 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 callbackfilteritrator iterator is an iterator that performs 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’s talk about the iterator first because it is related to array_ Filter () is so similar. array_ Filter () is the same. It uses a callback function to filter and judge.

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

Regex iterator believes that there is no need to explain. It uses regular expressions to filter and judge. Note here that we use an iterator_ to_ Array () function, which is also a function in SPL, is used to convert iterators into arrays. In fact, it solves the problem that we have to write foreach or while loops to demonstrate.

Through the learning of the derived classes of the above two filteritrators, I believe you are more interested in this filter iterator. However, the original filteritrator is an abstract class, that is, it cannot be instantiated directly. We can only write another class to inherit it and implement its core method 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 small partners must have understood that both the callbackfilteritrator and regexitator above are implementation classes that implement filteritrator and override the accept () method. They pass the constructor function to transmit the required data. In the process of using the core, CallbackFilterIterator calls the callback method that is passed in accept (), while RegexIterator is in the accept () to make regular expression judgement on the data of the internal iterator.

Infinite iterator infinite iterator

Infinite iterator? What the hell? It looks 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 the function of pointing the pointer back to the first data when next () reaches the last data. It feels like a loop queue, that is, if we traverse this infinite iterator without constraints, it will become an endless loop.

Limititerator number limit iterator

Just look at the name. Just as we often operate the page turning function of MySQL database, limititerator returns part of the data according to the start and offset interval 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)
//   }

No rewind iterator

The last iterator in the iterator iterator series to be introduced is the norewind iterator. Similarly, we can see some clues from the name that the iterator does not have the rewind () method, or 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) {
// }

As we saw earlier, in foreach (), the rewind () method is called at the beginning of each traversal to return the data pointer to the top. Similarly, iterator_ to_ The internal implementation of the array () method is similar. But if it is norewindiiterator, the second traversal will not have content, because its rewind () method does not take effect, or it is an empty method.

You can try to use the while () loop to test, rather than using iterator_ to_ Array () is clearer.

Multipleiterator Multiparallel iterator

After walking out of the iterator iterator, let’s take a look at an iterator that has nothing to do with it, that is, the iterator does not inherit or use the methods and functions related to the iterator iterator.

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 save some iterators internally, but note that these are not built-in iterators, which are different from iterator iterator. In addition, its expression is also different from that of 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 an iterator through attachterator(), judge whether the specified iterator exists through containsiterator(), or delete an iterator through detachterator(). However, the main feature is the result of traversal.

Whether it is key () or current (), the data returned is an array. In fact, this array is the corresponding content of each iterator. For example, the first key () returns the position of the subscript 0 of the first iterator, the subscript a of the second iterator and the subscript 0 of the third iterator. That is, it returns the index information of the first position of all iterators at once. Similarly, current () returns all the data information of the current location.

In addition, we can see that the internal data quantity of different iterators is different. The multipleiterator will only return the minimum data quantity. You can try it yourself.

Implement an iterator class yourself

After talking about so many iterators, should we simply implement an iterator that can make count () take effect and has the function of recursive implementation, which can set the cursor.

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 it is also explained in the notes. The most important thing is to implement the three interfaces of countable, recursive iterator and seekableitator. They correspond to count capability, recursion capability and cursor setting capability respectively.

summary

There are many things. The implementation of various iterators can be said to be a very important content in SPL. In addition to those 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. Racing continues!

Test code:

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

Reference documents:

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

Recommended Today

Seven solutions for distributed transactions

1、 What is distributed transaction Distributed transaction means that transaction participants, transaction supporting servers, resource servers and transaction managers are located on different nodes of different distributed systems. A large operation is completed by more than n small operations. These small operations are distributed on different services. For these operations, either all of them are […]