• 认真地记录技术中遇到的坑!

PHP 预定义迭代器接口:Traversable、Iterator、IteratorAggregate

PHP 悠悠 7个月前 (03-25) 235次浏览 0个评论

Traversable遍历接口

在介绍Iterator之前,不得不介绍PHP的另外一个用于遍历的预定义接口Traversable

Traversable接口是PHP内部的迭代器的抽象基类,只用于内部使用。一般我们用它来检测一个类是否可以使用foreach进行遍历。

$myarray = array('one', 'two', 'three');
$myobj = (object)$myarray;

if ( !($myarray instanceof \Traversable) ) {
    print "myarray is NOT Traversable";
}
if ( !($myobj instanceof \Traversable) ) {
    print "myobj is NOT Traversable";
}

foreach ($myarray as $value) {
    print $value;
}
foreach ($myobj as $value) {
    print $value;
}

上面的例子输出为:

myarray is NOT Traversable
myobj is NOT Traversable
one
two
three
one
two
three 

需要注意到,虽然array没有继承Traversable,但是它仍然可以使用foreach遍历,所以我们一般使用下面的方法检测一个变量是否能够用foreach遍历:

if (!is_array($items) && !$items instanceof \Traversable) {
    // 不能用foreach遍历,可以抛出异常
}

Iterator迭代器接口

定义

Iterator是PHP预定义的可在内部迭代自己的外部迭代器或类的接口。实现该接口的类,可以使用foreach进行遍历,该接口在内部是继承自上面介绍的Traversable接口。

Iterator接口的定义如下:

Iterator  extends Traversable  {

abstract public mixed current ( void ) // 返回当前元素
abstract public scalar key ( void ) // 返回当前元素的键
abstract public void next ( void ) // 向前移动到下一个元素
abstract public void rewind ( void ) // 返回到迭代器的第一个元素
abstract public boolean valid ( void ) // 检查当前位置是否有效
}

实现Iterator接口并且实现上面定义的5个遍历基本方法,它们分别控制着foreach遍历时的行为。

实现方法

下面展示如何实现Iterator接口,从而能够让foreach正确遍历自定义类:

<?php
class UIterator implements Iterator {
    private $position = 0;  // 定义遍历时候的索引
    private $data = array(
        'first_element',
        'second_element',
        'last_element'
    );

    // 构造函数,初始化位置索引
    public function __construct()
    {
        $this->position = 0;
    }

    // 初始化位置索引
    public function rewind()
    {
        var_dump(__METHOD__);
        $this->position = 0;
    }

    // 获取当前元素
    public function current()
    {
        var_dump(__METHOD__);
        return $this->data[$this->position];
    }

    // 返回当前元素的键
    public function key()
    {
        var_dump(__METHOD__);
        return $this->position;
    }

    // 返回下一个元素
    public function next()
    {
        var_dump(__METHOD__);
        ++$this->position;
    }

    // 返回当前元素是否有效
    public function valid()
    {
        var_dump(__METHOD__);
        return isset($this->data[$this->position]);
    }
}

$it = new UIterator();
foreach ($it as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

以上例子输出结果为:

string(18) "myIterator::rewind"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(0)
string(12) "first_element"

string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(1)
string(13) "second_element"

string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(2)
string(11) "last_element"

string(16) "myIterator::next"
string(17) "myIterator::valid"

显然foreach中各个方法调用的顺序如下:

  • 首次调用rewind方法获得开始的键
  • 调用valid方法查看当前索引是否有效
  • 调用current方法获得当前的元素
  • 调用key方法获得当前元素的键,如果foreach中没有使用$key=>$value的形式,则不会调用该方法
  • 重复上面的过程,知道valid方法返回false,键无效则结束

IteratorAggregate聚合式迭代器接口

定义

你可能会对上面的Iterator是PHP预定义的可在内部迭代自己的外部迭代器或类的接口疑惑,其实迭代器分为可在内部迭代和不可内部迭代,换种说法,可在内部迭代相当于可以在内部自定义实现,反之则不然。

IteratorAggregate接口创建外部迭代器的接口,仅仅只是返回一个外部迭代器实例,实现该接口并不能像Iterator自定义迭代行为。它的定义如下:

IteratorAggregate  extends Traversable  {
    abstract public Traversable getIterator ( void ) // 获取一个外部迭代器
}

显然,该接口只能返回一个迭代器实例,并不能像Iterator那样自定义迭代行为,如定义迭代元素等。

使用方法

<?php
class myData implements IteratorAggregate {
    public $property1 = "Public property one";
    public $property2 = "Public property two";
    public $property3 = "Public property three";

    public function __construct() {
        $this->property4 = "last property";
    }

    public function getIterator() {
        // 返回一个数组迭代器实例
        return new ArrayIterator($this);
    }
}

$obj = new myData;

foreach($obj as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

上面实例的输出结果如下:

string(9) "property1"
string(19) "Public property one"

string(9) "property2"
string(19) "Public property two"

string(9) "property3"
string(21) "Public property three"

string(9) "property4"
string(13) "last property"

可见返回一个ArrayIterator实例,就相当于把类的属性当成了一个数组。

其中ArrayIterator是PHP内部定义的迭代器,是一个数组迭代器,还记得上面的Iterator接口的那个实例吗?我们可以改一下使得它更简单:

<?php
class UIAIterator implements IteratorAggregate {
    private $position = 0;  // 定义遍历时候的索引
    private $data = array(
        'first_element',
        'second_element',
        'last_element'
    );

    public function getIterator()
    {
        return new ArrayIterator($this->data);
    }
}

$it = new UIAIterator();
foreach ($it as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

上面的类UIAIteratorUIterator的效果是一样的,都能够用foreach正确遍历类内部的data数组。而且UIAIterator更加简单。

当然,在实现IteratorAggregate接口的时候,除了可以返回ArrayIterator迭代器,还可以返回其他的PHP内部迭代器,比如SPL系列迭代器,甚至自定义迭代器。


喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址