# 预定义接口(Predefined Interfaces)

1. Traversable
2. Iterator
3. [IteratorAggregate](http://php.net/manual/zh/class.iteratoraggregate.php)（聚合式迭代器）接口
4. Generator　生成器
5. ArrayAccess
6. Serializable
7. Closure

## [IteratorAggregate](https://www.gitbook.com/book/xiaoxiami/phper/edit)（聚合式迭代器）接口

接口摘要

```php
IteratorAggregate extends Traversable {
/* 方法 */
abstract public Traversable getIterator ( void )
}
```

个接口实现了一个功能——创建外部迭代器，具体怎么理解呢，当我们使用**foreach**对对象进行便遍历的时候，如果没有继承**IteratorAggregate**接口，遍历的是对象中所有的public属性(只能是public $var这种形式)。要是继承了**IteratorAggregate**，会使用类中实现的**getIterator**方法返回的对象，这里要注意返回的一定要是一个**Traversable**对象或者扩展自**Traversable**的对象，否则会抛出异常

**示例：**

ArrayIterator 对数组进行了Iterator封装

```php
class My{
 private $_data = [
 'a' => '燕睿涛',
 'b' => 'yanruitao',
 'c' => 'LULU',
 ];

 public function getIterator()
 {
   return new ArrayIterator($this->_data);
 }
}
$obj = new My;
foreach ($obj as $key => $value) {
 echo "$key => $value\n";
}
//输出结果为空
```

改造

```php
class My implements IteratorAggregate {
 private $_data = [
 'a' => '燕睿涛',
 'b' => 'yanruitao',
 'c' => 'LULU',
 ];

 public function getIterator()
 {
 return new ArrayIterator($this->_data);
 }
}
$obj = new My;
foreach ($obj as $key => $value) {
 echo "$key => $value\n";
}
```

结果：

```php
a => 燕睿涛
b => yanruitao
c => LULU
```

示例：

Countable 的目的是能够使用php count函数来统计日志的行数

Iterator 迭代器需要实现current() key() next() rewind() valid()方法

foreach 之前，先rewind() ，将遍历的指针重置到遍历的开头，调用next() 将指针移动到下一个位置，current() 则可以获得当前指针指向数据，每次循环都会调用valid()方法，如果返回false，则证明遍历结束

```php
<?php
/**
 * 提取 access.log 各行中的 ip 和 request-url ，通过 foreach 遍历显示
 */
 class AccessRecord {
    private $segments = [];
    public function __construct($row) {
        preg_match('/((?:\d{1,3}.){3}\d{1,3}).*?"[A-Z]{3,}\s(.*?)\s/', $row, $rs);
        $this->segments = array($rs[1], $rs[2]);
    }
    public function getIp() {
        return $this->segments[0];
    }
    public function getUrl() {
        return $this->segments[1];
    }
}


class AccessLogIterator implements Countable, Iterator {
    private $filepath; 
    private $fp;
    private $currentContent; //当前行的内容
    private $linePointer; //当前行的指针（行号）
    private $count; //文件行数总计


    public function __construct($filepath) {
        $this->filepath = $filepath;
        $this->init();
    }

    /**
      * 初始化，打开文件，重置文件指针
      */
    private function init() {
        $this->linePointer = -1;
        $this->currentContent = NULL;
        if(is_null($this->fp)) {
            $this->fp = fopen($this->filepath, 'rb');
        }
        rewind($this->fp);
    }

    /**
      * Countable 接口中需实现的方法，用于 count($obj) 返回结果
      */
    public function count() {
        if(is_null($this->count)) {
            $this->count = intval(shell_exec("wc -l '{$this->filepath}'"));
        }
        return $this->count;
    }        

    public function current() {
        return $this->currentContent;
    }

    public function key() {
        return $this->linePointer;
    }

    public function next() {
        ++$this->linePointer;
        if($this->linePointer < $this->count()) {
            $this->currentContent = new AccessRecord(fgets($this->fp));
        }
    }

    /**
      * foreach 开始时调用 
      */
    public function rewind() {
        $this->init();
        $this->next();
    }

    public function valid() {
        return $this->linePointer < $this->count();
    }

}

class AccessLogIteratorAggregate implements IteratorAggregate {

    public function asdf() {

    }

    public function getIterator() {
        return new AccessLogIterator('./a.log');
    }


}

$t = new AccessLogIteratorAggregate();

$i=0;
foreach($t as $key => $record) {

    echo $key.': '.$record->getIp()."\t".$record->getUrl().'<br />';
}
```

a.log

```
123.151.47.117 - - [16/Feb/2014:00:00:02 +0800] "GET / HTTP/1.1" 200 32535 "http://www.musicworld.com/" "Mozilla/5.0 (compatible; Sosospider/2.0; +http://help.soso.com/webspider.htm)"
220.171.166.19 - - [16/Feb/2014:00:00:06 +0800] "GET /search/mp3?title=%E5%86%AC%E5%A4%A9%E9%87%8C%E7%9A%84%E4%B8%80%E6%8A%8A%E7%81%AB&search=%E6%90%9C%E7%B4%A2 HTTP/1.1" 200 10307 "http://www.musicworld.com/search/mp3?title=%E7%9C%9F%E5%BF%83%E8%8B%B1%E9%9B%84&search=%E6%90%9C%E7%B4%A2" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1"
220.171.166.19 - - [16/Feb/2014:00:00:08 +0800] "POST /pm/check HTTP/1.1" 200 11 "http://www.musicworld.com/search/mp3?title=%E5%86%AC%E5%A4%A9%E9%87%8C%E7%9A%84%E4%B8%80%E6%8A%8A%E7%81%AB&search=%E6%90%9C%E7%B4%A2" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1"
```

**思考：为什么不用 file() 函数读到数组里面再遍历？**

```php
$rows = file('/log.file');
foreach($rows as $row) {
    echo $row;
}
```

有可能会出现一下错误(内存不够用)：

```
PHP Fatal error:  Allowed memory size of 33554432 bytes exhausted (tried to allocate 262144 bytes) in  /bigstupid.php
```

**解决办法：**

用生成器！

## 生成器（Generator）

* 生成器提供了一种更简单的可实现 Iterator 同样功能的方法。
* 你可以通过生成器逐条**产生（yield）**&#x4F9B; foreach 遍历的数据，而且不需要事先在内存中建立整个数组。
* 生成器像一个函数，但是使用关键词 **yield** 返回数据。

> Generator 是 Traversable 的子类

示例：

```php
<?php

/**
 * yield 
 */

function lineGenerator($file) {
    $fp = fopen($file, 'rb');
    try {
        while($line = fgets($fp)) {
            yield $line;
        }

    } finally {
        fclose($fp);
    }
}

$lines = lineGenerator("a.log"); 
foreach($lines as $line) {
    echo $line;
}
```

进一步`key=>value`形式

```php
<?php

/**
 * 产生 key=>value
 */

function lineGenerator($file) {
    $fp = fopen($file, 'rb');
    try {
        while($line = fgets($fp)) {
            $lineParts = explode(' ', $line, 2);
            yield $lineParts[0] => $lineParts[1];
        }

    } finally {
        fclose($fp);
    }
}

foreach(lineGenerator("./a.log") as $ip=>$line) {

    echo $ip .' => '. $line;
}
```

> IteratorAggregate 与 yield 的结合示例
>
> getIterator 需要返回一个Traversable 对象, 生成器yield 就是一个Traversable的对象的子类
>
> ```php
> class Test implements IteratorAggregate {
>     protected $data;
>  
>     public function __construct(array $data) {
>         $this->data = $data;
>     }
>  
>     public function getIterator() {
>         foreach ($this->data as $key => $value) {
>             yield $key => $value;
>         }
>     }
> }
> ```

## 总结

* 实现了 Traversable 的对象都可以通过 foreach 实现遍历；
* 生成器函数实现 foreach 更简单，适用于简单的数据和逻辑；
* Iterator 实现 foreach 可定制性更高，适用于较复杂的逻辑；
* IteratorAggregate 可返回一个外部迭代器，可对数据本身及相关逻辑做更统一的封装。

## 资料

[PHP之预定义接口详解](http://www.jb51.net/article/70193.htm)
