预定义接口(Predefined Interfaces)

  1. Traversable

  2. Iterator

  3. IteratorAggregate(聚合式迭代器)接口

  4. Generator 生成器

  5. ArrayAccess

  6. Serializable

  7. Closure

IteratorAggregate(聚合式迭代器)接口

接口摘要

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

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

示例:

ArrayIterator 对数组进行了Iterator封装

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";
}
//输出结果为空

改造

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";
}

结果:

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

示例:

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

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

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

<?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() 函数读到数组里面再遍历?

$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)供 foreach 遍历的数据,而且不需要事先在内存中建立整个数组。

  • 生成器像一个函数,但是使用关键词 yield 返回数据。

Generator 是 Traversable 的子类

示例:

<?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

/**
 * 产生 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的对象的子类

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之预定义接口详解

Last updated