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