PHP 对象迭代与 Iterator 接口
经过 PHP 如何迭代一个对象 ( 上 ) 和 PHP 对象迭代与 get_object_vars() 两章节的学习,我们知道,当使用一 foreach 迭代一个对象时,会首先调用 get_object_vars()
方法返回一个由当前作用域下对象的可见属性组成的关联数组,然后再对这个数组进行迭代。
当然了,这是在该对象没有继承和实现任何 Interator 接口的情况下,如果实现了 Interator 接口,那么行为又会不一样了。
Interator 接口与 SPL
SPL,Standard PHP Library 的缩写,中文名 「 PHP 标准库 」 是从 PHP 5.0 起内置的组件和接口,且从 PHP5.3 已逐渐的成熟。
SPL 在所有的 PHP5 开发环境中被内置,同时无需任何设置。
其实,SPL 的也是 Standard Problem Library 的缩写,哈哈,标准问题库的缩写,这个,才最具代表意义,一些问题的标准答案库。
PHP SPL 内置了大量的接口与类用于对象的遍历(迭代)。具体呢,如下
AppendIterator ArrayIterator CachingIterator CallbackFilterIterator DirectoryIterator EmptyIterator FilesystemIterator FilterIterator GlobIterator InfiniteIterator IteratorIterator LimitIterator MultipleIterator NoRewindIterator ParentIterator RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator
当然了,上面这些只是在标准库中定义的,还有另一些预定义的用于迭代相关的接口和类
Traversable Iterator IteratorAggregate
这么多类和接口中,最重要的莫过于 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 bool valid ( void ) }
Traversable 接口
Iterator
接口扩展自 Traversable
这个预定义接口,而 Traversable
接口呢?并没有任何属性和方法,它只是一个空接口,用于表示某个对象是可迭代的。也就是说,用于标示一个对象是可以被 foreach
迭代的。
Traversable { }
该接口的一般用法如下
if ( $obj instanceof Traversable ) { // 对象可能可以被迭代 }
注意: 虽然 PHP 中所有的类都可以使用 foreach 来迭代,但并非所有的类都实现了 Traversable 接口。
<?php $obj = new stdClass(); var_dump($obj instanceof Traversable);
输出结果为
[yufei@www.twle.cn helloworld]$ php d.php bool(false)
Iterator 接口
让我们回到 Iterator 接口,我们看看该接口定义的几个方法到底啥意思
方法 | 说明 |
---|---|
public mixed current ( void ) | 返回当前(游标)位置的元素值 |
public scalar key ( void ) | 返回当前(游标)位置 |
void next ( void ) | 当当前(游标)位置移到下一个元素 |
public void rewind ( void ) | 重置当前(游标)位置到第一个元素 |
public bool valid ( void ) | 返回当前(游标)位置是否可用,如果返回 false,那么 foreach 就会停止 |
这些方法的作用都很好理解,但是它们的实际意义,你可能有些迷茫
迭代迭代,就是有有一组东西需要从头到尾的一个一个遍历。假设我们有一组 6 个数字 [11,15] ,当前位置为 3 ,如下图所示
对于 foreach 循环遍历一个对象来说,最重要的有 4 个方法:
-
在当前位置上,首先需要调用 valid() 方法判断是否可用,如果不可用则退出循环
-
在当前位置上,可迭代元素必须实现两个方法,
current()
返回当前(游标)位置的值,而key()
方法则返回当前位置。也就是对于
foreach($obj as $k => $v )
,current()
返回$v
的值,key()
返回$k
的值 -
每次迭代后,位置就要向下移动,那么,就要调用
next()
方法 -
如果要重新遍历,那么就需要调用 rewind() 来重置位置
所以,使用一个 foreach 来遍历一个对象,如果使用 for
语句重写,那么大概就是下面这样子
$obj = new stdClass; for($obj->rewind();;) { if ( $obj->valid() ) { $value = $obj->current(); $key = $obj->key(); // 一些其它逻辑 } else { break; } $obj->next(); }
接下来我们写一段看看如何重写前一章节的代码实现一个迭代器
<?php class Person implements Iterator { public $name = "语飞"; public $sexy = "男"; protected $age = 30; private $married = "no"; private $attributes = []; public function __construct(){ $attributes = get_object_vars($this); unset($attributes['attributes']); $this->attributes = $attributes; } public function rewind() { echo "rewinding\n"; reset($this->attributes); } public function current() { $var = current($this->attributes); echo "current: $var\n"; return $var; } public function key() { $var = key($this->attributes); echo "key: $var\n"; return $var; } public function next() { $var = next($this->attributes); echo "next: $var\n"; return $var; } public function valid() { $key = key($this->attributes); $var = ($key !== NULL && $key !== FALSE); echo "valid: $var\n"; return $var; } } $me = new Person(); foreach($me as $k => $v ) { echo $k," => ", $v, "\n"; }
运行上面这段代码,输出结果如下
[yufei@www.twle.cn helloworld]$ php d.php rewinding valid: 1 current: 语飞 key: name name => 语飞 next: 男 valid: 1 current: 男 key: sexy sexy => 男 next: 30 valid: 1 current: 30 key: age age => 30 next: no valid: 1 current: no key: married married => no next: valid:
IteratorAggregate 接口
Iterator 接口很好用,但是,它有一个缺点,5 个方法都是虚拟方法,也就是说如果我们直接实现 Interator 接口必须 5 个方法都要实现,否则类就不能实例化,这是很悲剧的。
还好,好在 PHP 也意识到了这一点,提供了 IteratorAggregate 接口,它默认实现了 5 个方法,只需要我们实现另一个方法 getIterator()
就好
IteratorAggregate 接口的定义如下
IteratorAggregate extends Traversable { /* Methods */ abstract public Traversable getIterator ( void ) }
IteratorAggregate::getIterator()
方法需要返回一个 Traversable
对象。
注意:该方法需要返回的是
Traversable
对象,返回数组是不可以的。
现在,我们用这个接口来改写下上面这个实例吧
<?php class Person implements IteratorAggregate { public $name = "语飞"; public $sexy = "男"; protected $age = 30; private $married = "no"; public function getIterator(){ return new ArrayIterator($this); } } $me = new Person(); foreach($me as $k => $v ) { echo $k," => ", $v, "\n"; }
运行结果如下
[yufei@www.twle.cn helloworld]$ php d.php name => 语飞 sexy => 男