# 预定义接口(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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://phper.shujuwajue.com/php-gao-ji-te-xing/yu-ding-yi-jie-53e328-predefined-interfaces.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
