PHP迭代器学习笔记

最近碰到了一个问题,PHP中如何遍历对象(迭代对象)。

第一部分 遍历对象

一、问题描述

我们知道foreach可以遍历数组,也可以遍历对象,但是默认情况下只能遍历对象public属性。

class User
{
    public  $name = 'salmonl';
    private $sex  = 'm';
    private $age  = 30;

    public static function getAge()
    {
        return $this->age;
    }
}

$user = new User();
var_dump($user);

foreach ($user as $key => $value) {
    echo $key, '==>', $value, PHP_EOL;
}

以上输入结果:

object(User)#1 (3) {
  ["name"]=>
  string(7) "salmonl"
  ["sex":"User":private]=>
  string(1) "m"
  ["age":"User":private]=>
  int(30)
}

name==>salmonl

我们可以发现只输出了public权限的属性,那么如果想输出全部属性,怎么处理呢?

二、解决办法一
在类中定义一个公有方法

public function iterateVisible() {
       echo "MyClass::iterateVisible:\n";
       foreach($this as $key => $value) {
           print "$key => $value\n";
       }
    }

三、解决办法二
让对象所属的类实现Iterator接口(原理可参考第二部分)

class Test implements Iterator
{
    private $item = [
        'id'   => 1,
        'name' => 'php'
    ];

    public function __construct($item)
    {
        if (is_array($item)) {
            $this->item = $item;
        }
    }
 
    public function rewind() {
        reset($this->item);
    }
 
    public function current() {
        return current($this->item);
    }
 
    public function key() {
        return key($this->item);
    }
 
    public function next() {
        return next($this->item);
    }
 
    public function valid() {
        return ($this->current()!== false);
    }
}

//测试
$test = new Test(['php', 'golang', 'lua', 'python']);
foreach ($test as $k => $v) {
    echo $k, '=>', $v, PHP_EOL;
}

以上输出:
0=>php
1=>golang
2=>lua
3=>python

第二部分 迭代器

一、相关概念和原理
0、什么是迭代器
PHP通过实现Iterator接口, 封装一个自己的类,这个类是可迭代的,称为迭代器

1、迭代器的好处。
可以让对象自行决定如何遍历以及每次遍历时那些值可用。遍历迭代器对象,用foreach、while都可以。

2、Iterator接口

Iterator extends Traversable {
/* 方法 */
abstract public current ( void ) : mixed
abstract public key ( void ) : scalar
abstract public next ( void ) : void
abstract public rewind ( void ) : void
abstract public valid ( void ) : bool
}

接口中定义了5个方法:
Iterator::current — 返回当前元素
Iterator::key — 返回当前元素的键
Iterator::next — 向前移动到下一个元素
Iterator::rewind — 返回到迭代器的第一个元素
Iterator::valid — 检查当前位置是否有效

3、通过foreach来遍历迭代器对象的时候会自动调用Iterator接口提供的5个方法

调用顺序: rewind/next->valid->current->key(如果是第一次就是rewind, 否则是next)。实际例子可参考官网

通过在相关方法中设置返回那些属性

二、迭代器应用场景
0、遍历对象
可见第一部分相关内容

1、要处理的结果集占用大量内存
1.0 定义一个功能和range一样的迭代器

class Xrange implements Iterator {
    protected $start;
    protected $limit;
    protected $step;
    protected $current;

    public function __construct($start, $limit, $step = 1) {
        $this->start = $start;
        $this->limit = $limit;
        $this->step  = $step;
    }

    public function rewind() {
        $this->current = $this->start;
    }

    public function next() {
        $this->current += $this->step;
    }

    public function current() {
        return $this->current;
    }

    public function key() {
        return $this->current + 1;
    }

    public function valid() {
        return $this->current <= $this->limit;
    }
}

// test case
foreach (new Xrange(0, 9) as $key => $val) {
    echo $key, ' => ', $val, PHP_EOL;
}

echo '******************************', PHP_EOL;
// range function
foreach (range(0, 9) as $key => $number) {
    echo $key, ' => ', $number, PHP_EOL;
}

以上代码运行结果

[root@izj6cfhaw27k49x8usszs3z coroutine]# php xrange.php
1 => 0
2 => 1
3 => 2
4 => 3
5 => 4
6 => 5
7 => 6
8 => 7
9 => 8
10 => 9
******************************
0 => 0
1 => 1
2 => 2
3 => 3
4 => 4
5 => 5
6 => 6
7 => 7
8 => 8
9 => 9

1.1 占用内存差别

// range
$startMemory = memory_get_usage();
$arr = range(0, 500000);
echo 'range(): ', memory_get_usage() - $startMemory, " bytes\n";

unset($arr);

// xrange
$startMemory = memory_get_usage();
$arr = new Xrange(0, 500000);
echo 'xrange(): ', memory_get_usage() - $startMemory, " bytes\n";

以上代码执行结果

[root@izj6cfhaw27k49x8usszs3z coroutine]# php xrange.php
range(): 72194960 bytes
xrange(): 440 bytes

1.2、执行结果分析
原生的range函数返回的是全量的数组,占用内存比较多;迭代器Xrange返回的是对象,只是当前对象占用的内存。

三、常用迭代器
0、官方SPL中定义了很多常用迭代器(点击查看)

参考:
PHP手册遍历对象
PHP协程
segmentfault:php 迭代接口的作用
Wikipedia: Iterator
Wikipedia: 迭代器

发表评论

电子邮件地址不会被公开。 必填项已用*标注