因此,对付迭代器的观点,我们这里就不会多说了,本日的紧张内容便是来理解一下 SPL 扩展中都包含哪些迭代器以及它们的功能效果。其余,上一篇文章中我们打仗过的数组迭代器 ArrayIterator 由于已经学习过了,也就不放在这里讲了。此外还有文件目录干系的迭代器,也会放在和其干系的文件目录操作的文章中讲解,包括下面学习的这些迭代器还有不少都有相对应的 递归式 的迭代器,比如我们下面要讲到的 CachingIterator 、 FilterIterator 等等,都有它们对应的 RecursiveCachingIterator 、 RecursiveFilterIterator 类,这个大家就自己去研究下吧,带递归迭代器,也便是多了两个方法 getChildren() 和 hasChildren() 而已,末了我们还会实现一个自己的迭代器类,个中就会讲到递归这块。
IteratorIterator 包装迭代器首先我们来看一下什么是包装迭代器。它本身也是个迭代器,但是在实例化的时候,又必须给他再通报进来一个迭代器并保存在内部,是一个内部迭代器 InnerIterator 。对付它自身的那些迭代器接口函数来说,实在都是转发调用的那个内部迭代器干系的操作函数。觉得上实在就有点像是一个装饰器模式,我们可以通过继续 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
从代码中可以看出,它的布局参数必须还得是一个迭代器,本身的参数署名便是须要一个实现了 Traversable 接口的工具。Traversable 接口是所有迭代器所必须要实现的接口。
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
我们自己写了一个 OutIterator 类并继续自 IteratorIterator 类,然后重写所有迭代器干系的方法函数。在这些函数中,增加一些输出调试信息,末了通过 foreach 来遍历迭代器。可以看出,foreach 在判断工具是否可迭代后,就会像我们利用 while 遍历迭代器一样地去调用对应的迭代器方法函数。这个例子相当地直不雅观,也非常有助于我们理解迭代器这堆方法函数到底在干嘛。
var_dump($iterator->getInnerIterator());// object(ArrayIterator)#5 (1) {// ["storage":"ArrayIterator":private]=>// array(3) {// [0]=>// int(1)// [1]=>// int(2)// [2]=>// int(3)// }// }
通过 getInerIterator() 方法我们就可以得到包装迭代器内部的那个迭代器工具。这里很清晰地就能看到我们给它内部放置的那个迭代器干系的信息。
接下来,我们再学习一些派生自 IteratorIterator 类的迭代器。也便是说,它们都是继续自 IteratorIterator 这个包装迭代器的,并在它的根本之上又增加了不少新奇的功能。
AppendIterator 追加迭代器追加迭代器,很奇怪的名字,先来看看它是做啥的。
$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: 1var_dump($appendIterator->getIteratorIndex()); // NULL
是的,你没看错,这个追加迭代器的功能便是可以在它里面保存多个内部迭代器。我们可以通过 append() 方法不断地添加,通过 getIteratorIndex() 可以查看到当前利用或遍历到的是哪个一个内部迭代器。
如果要获取内部迭代器工具的话,虽然也有继续自 IteratorIterator 的 getInnerIterator() 方法,但最好利用另一个方法。
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() 可以一个数组形式的凑集来返回所有的内部迭代器。
CachingIterator 缓存迭代器从英文名字就可以看出来,缓存迭代器。
$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)// }
它比较有特色的便是这个 getCache() 方法,从上面的测试代码中大家看出什么问题了吗?没错,当我们遍历一次迭代器之后,内部迭代器的数据信息会缓存到 getCache() 这个方法里面返回的数组中。我们在遍历之前调用 getCache() 方法是没有任何内容的。其余,通过布局参数的第二个参数,我们可以指定缓存数据的信息内容,在这里我们利用的是 CachingIterator::FULL_CACHE ,也便是缓存全部内容。
FilterIterator 过滤迭代器过滤这个词熟习不,array_filter() 这个函数也是针对数组进行过滤操作的。同样地,FilterIterator 迭代器也是实现类似的效果。不过在学习利用这个 FilterIterator 之前,我们先学习一下它的两个派生类。
$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
CallbackFilterIterator 迭代器是通过我们在布局参数的第二个参数指定的回调函数来进行过滤操作的一个迭代器。如果要让数据通过,返回 true ,否则就返回 false 。先讲这个迭代器正是由于它和 array_filter() 实在是太像了。array_filter() 也是一样的通过一个回调函数来进行过滤判断的。
$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 相信也不用多阐明了,它便是通过正则表达式来进行过滤判断的。在这里须要把稳的是,我们利用了一个 iterator_to_array() 函数,它也是 SPL 中的一个函数,浸染便是将迭代器转换为数组,实在也便是办理我们都要写 foreach 或者 while 循环来演示的麻烦。
通过上面两个 FilterIterator 的派生类的学习,相信大家对付这个过滤迭代器更加有兴趣了。不过,这个原始的 FilterIterator 是一个抽象类哦,也便是说,它是不能直接实例化的,我们只能去再写一个类来继续它,并且要实现它的一个核心方法 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)// }
不幼年伙伴一定已经明白了,不管是上面的 CallbackFilterIterator 还是 RegexIterator ,都是实现了 FilterIterator 的一个实现类,它们都重写了 accept() 方法。它们通过布局函数的来通报须要的数据,在核心利用的过程中 CallbackFilterIterator 便是在 accept() 中调用了那个通报进来的回调方法,而 RegexIterator 则是在 accept() 中对内部迭代器的数据进行了正则表达式的判断。
InfiniteIterator 无限迭代器无限迭代器?什么鬼,貌似很高大上。这是一个坑,要小心哦。
$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// ………………// ………………
说白了,类似实现了让 next() 到末了一个数据的时候就将指针指回第一条数据的功能。有点像循环行列步队的觉得,也便是说,如果我们没有限定条件的话,遍历这种无限迭代器,它将变成去世循环一贯一直地循环下去。
LimitIterator 数量限定迭代器看名字就知道了,就像我们常常操作 MySQL 数据库做的翻页功能一样,LimitIterator 也是根据起始和偏移区间值返回一部分数据的。
$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 无重回迭代器
末了一个要先容的 IteratorIterator 系列中的迭代器便是这个 NoRewindIterator 。同样地从名字中我们可以看出一些端倪,那便是这个迭代器是没有 rewind() 方法的,或者说,这个方法是不起浸染的。
$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) {// }
前面我们看到过,在 foreach() 时,每次遍历开始时都会调用 rewind() 方法让数据指针回到最顶部。同样地,iterator_to_array() 方法在其内部实现也是这样类似的步骤。但如果是 NoRewindIterator 的话,第二次遍历就不会有内容了,由于它的 rewind() 方法是不生效的,或者说是一个空的方法。
大家可以算大考试测验用 while() 循环那种办法来测试一下,比利用 iterator_to_array() 更加清晰一些。
MultipleIterator 多并行迭代器走出了 IteratorIterator 之后,我们来看一个和它没什么关系的迭代器,也便是说,这个迭代器没有继续或者利用 IteratorIterator 干系的方法函数内容。
从名字来说,Multiple 是多个的意思,难道是内部放了多个迭代器?这不是和 AppendIterator 一样了。好吧,我承认,它确实在内部保存了一些迭代器,但把稳,这些不是内置迭代器,和 IteratorIterator 是不同的哦。其余,它的表现形式也和 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"// }
我们可以通过 attachIterator() 添加迭代器,通过 containsIterator() 判断指定的迭代器是否存在,也可以通过 detachIterator() 删除某个迭代器。不过最紧张的特点还是在遍历的结果。
不管是 key() 还是 current() ,返回的数据都是一个数组。实在这个数组便是每个迭代器对应的内容,比如第一个 key() 返回的是第一个迭代器的下标 0 的位置,第二个迭代器下标 a 和第三个迭代器下标 0 的位置。也便是说,它一次返回了所有迭代器第一个位置的下标信息。同理,current() 返回的也是当前这个位置的所有数据信息。
其余,我们可以看到,不同的迭代器的内部数据数量是不同的,MultipleIterator 只会以最少的那条数据的数量进行返回,这个大家可以自己考试测验下哦。
自己实现一个迭代器类讲了那么多迭代器,我们要不要自己也来大略地实现一个可以让 count() 生效的,并且有递归实现功能的,可以设置游标的迭代器。
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)// }
关于代码不多阐明了,注释里也有解释,最紧张的便是要实现 Countable, RecursiveIterator, SeekableIterator 这三个接口。它们分别对应的便是 count 能力、递归能力、设置游标的能力。
总结东西不少吧,各种迭代器的实现可以说是 SPL 中的一个非常主要的内容。除了本日先容的这些之外,还有别的一些迭代器我们将在干系的文章中独立讲解。光是本日的内容估计就不好消化了,抓紧接管吧,飙车还在连续哦!
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/01/source/5.PHP的SPL扩展库(三)迭代器.php
参考文档:
https://www.php.net/manual/zh/spl.iterators.php