迭代器,让对象可用 foreach,for,while方法

啥是 Iterator

是一种 php 提供的接口标准,可以将不同的类迭代调用统一成一样的方法,打个比方(话说谁叫比方?): 这是调用 mysql 时取出资源的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$dbms='mysql';
$host='localhost';
$dbName='wechat';
$user='root';      
$pass='123123';
$dsn="$dbms:host=$host:33060;dbname=$dbName;";

try {
    $dbh = new PDO($dsn, $user, $pass); //初始化一个PDO对象
    foreach ($dbh->query('SELECT * from migrations;') as $row) {
        print_r($row);
    }

    $dbh = null;
} catch (PDOException $e) {
    die ("Error!: " . $e->getMessage() . "<br/>");
}

而读取目录下所有文件的方法是:

1
2
3
4
5
$dirPath = '/Users/gpf/Desktop';
$list = opendir($dirPath);
while($filename = readdir($list)){
    echo $filename . PHP_EOL;
}

而读取文件内容的方法是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$filePath = '/Users/gpf/Desktop/test.php';

$handler = @fopen($filePath,'r');
if(!$handler){
    die('error');
}
while(!feof($handler)){
    $line = fgets($handler);
    echo $line;
}

foreach(new SplFileObject($filePath) as $line){
    echo $line . PHP_EOL;
}

都是做的循环的事但是不统一,这就很烦躁,因为同样一件事你需要用不同的方法去实现,一个字要用四种写法 这种情况下在 php51 之后开发组也意识到问题了,于是就出现了 SPL,就有了一个通用的 Iterator 这个迭代器就是让不同的资源操作变得相同,这些接口可以自己实现,也可以用官方提供的一些迭代器 比如读取文本的迭代器就可以这样获取文本内容了:

1
2
3
4
5
6
7
8
$filePath = '/Users/gpf/Desktop/test.php';
$handler = new SplFileObject($filePath);
foreach($handler as $line){
    echo $line . PHP_EOL;
}
//当然也可以直接跳行查找
$handler->seek(4);
var_dump($handler->current());

这样调用就显得比较统一了,那么

怎么使用 Iterator?

这里参照了php 文档阮一峰的博客 写的一个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class IteratorDemo implements Iterator{
    private $attributes = [];

    protected $valid = FALSE;

    function __construct(array $array)
    {
        $this->attributes = $array;
    }

    function rewind()
    {
        var_dump(__METHOD__);
        $this->valid = (FALSE !== reset($this->attributes));
    }

    function current()
    {
        var_dump(__METHOD__);
        return current($this->attributes);
    }

    function key()
    {
        var_dump(__METHOD__);
        return key($this->attributes);
    }

    //如果 next 返回 false 循环将终止
    function next()
    {
        var_dump(__METHOD__);
        $this->valid = (FALSE !== next($this->attributes));
    }

    function valid()
    {
        var_dump(__METHOD__);
        return $this->valid;
    }
}

$arr  = [
    'red' => 'stop',
    'green' => 'run',
    'yellow' => 'wait'
];
$demo = new IteratorDemo($arr);
var_dump($demo);
foreach ($demo as $key => $value){
    echo $key  . '=>' . $value . PHP_EOL;
}

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
object(IteratorDemo)#1 (2) {
  ["attributes":"IteratorDemo":private]=>
  array(3) {
    ["red"]=>
    string(4) "stop"
    ["green"]=>
    string(3) "run"
    ["yellow"]=>
    string(4) "wait"
  }
  ["valid":protected]=>
  bool(false)
}
string(20) "IteratorDemo::rewind"
string(19) "IteratorDemo::valid"
string(21) "IteratorDemo::current"
string(17) "IteratorDemo::key"
red=>stop
string(18) "IteratorDemo::next"
string(19) "IteratorDemo::valid"
string(21) "IteratorDemo::current"
string(17) "IteratorDemo::key"
green=>run
string(18) "IteratorDemo::next"
string(19) "IteratorDemo::valid"
string(21) "IteratorDemo::current"
string(17) "IteratorDemo::key"
yellow=>wait
string(18) "IteratorDemo::next"
string(19) "IteratorDemo::valid"

这就实现了一个通用的 Iterator 接口,当然这是是为了实现而实现,中间可以做很多事情的,这要发挥一下想象力

在程序里经常看到 FooIterator 这类的对象,就是基于上面写的 Iterator 进行的延伸,比如刚才我们 例子中用到的SplFileObject,SimpleXMLIterator,ArrayIterator这些类,都是大同小异的, 不过是根据实现的迭代器添加一些更适用的方法,比如一个DirectoryIterator中就是继承了SplFileInfo的方法 并且实现了 Iterator 接口,再添加了一些自己的方法,有用 idea 的可以用代码追踪看一下也是蛮有意思的

参考资料:

  1. Iterator(迭代器)接口
  2. PHP SPL笔记