PHP - 在Iterator对象上的foreach()之后保留内部指针

时间:2017-11-03 08:07:54

标签: php arrays foreach iterator

以下是PHP文档中有关Iterator的代码,其中添加了几行以显示位置。

如您所见,该对象有3个元素,位置位于foreach()之前的第2个元素($ this-> position = 1),

在foreach()之后,位置变为无效值($ this-> position = 3)。

class myIterator implements Iterator {
    private $position = 0;
    private $array = array(
        "firstelement",
        "secondelement",
        "lastelement",
    );  

    public function __construct() {
        $this->position = 0;
    }

    public function rewind() {
        var_dump(__METHOD__);
        $this->position = 0;
    }

    public function current() {
        var_dump(__METHOD__);
        return $this->array[$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->array[$this->position]);
    }

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

}

$it = new myIterator;

$it->next(); 
var_dump($it->showPosition());   //shows 1

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

var_dump($it->showPosition()); //shows 3 which is an invalid value.

在显示的foreach() doccument中:

  

注意:在PHP 5中,....在PHP 7中,foreach不使用内部数组指针。

我使用PHP7,显然上面的示例代码显示内部点在foreach()之后发生了变化。

我的问题是 - 是否可以在foreach()之后保留原始位置?

我理解一种可能的方法是添加变量以记住foreach()之前和foreach()之后的位置,手动设置位置。但它似乎与foreach()doc建议的内容相矛盾。

1 个答案:

答案 0 :(得分:0)

循环后你的Itterator实现中的指针是不正确的,因为你没有设置/重置最后一次调用valid()的值来自foreach循环它保留的最后一个增量值数组边界(在本例中为3),这里最简单的方法是设置指针null,这样就反映了PHP的行为,(在迭代器中使用本机数组函数时得到的结果) ,稍后会详细说明)

有一点需要注意,正如您所指出的那样,PHP7有一个变化

  

注意:在PHP 5中,当foreach首次开始执行时,内部   数组指针自动重置为第一个元素   阵列。这意味着您不需要在a之前调用reset()   foreach循环。因为foreach依赖于PHP中的内部数组指针   5,在循环内更改它可能会导致意外行为。在PHP中   7,foreach不使用内部数组指针。

我个人认为这不是一个大问题,除非你继续使用内部指针。根据我的经验,这并没有发生太多。

现在修复Iterator的实现:

选项1:

让PHP通过使用本机数组函数来处理指针:

class myIterator implements Iterator {

    private $array = array(
        "firstelement",
        "secondelement",
        "lastelement",
    );  

    public function __construct() {
        reset( $this->array );
    }

    public function rewind() {
        reset( $this->array );
    }

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

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

    public function next() {
       next( $this->array );
    }

    public function valid() {
        return isset( $this->array[$this->key()] );
    }

    //alias of key()
    public function showPosition() {
        return $this->key();
    }
}


echo str_pad('= NATIVE TRACKED POINTER =', 60, '=') ."\n";

$it = new myIterator;

$it->next(); //move to index 1

var_dump($it->key());   //<-- should print 1

foreach($it as $key => $value) {}

var_dump($it->key()); //<--shows NULL,

echo str_pad('= NATIVE ARRAY POINTER =', 60, '=') ."\n";

$array = array(
    "firstelement",
    "secondelement",
    "lastelement",
 );  

 next($array); //move to index 1

var_dump(key($array)); //<-- should print 1
foreach( $array as $key => $value ){}
var_dump(key($array));  //<--  NULL (PHP < 7),  1 PHP 7+

此输出(在PHP7.0.1中)

= NATIVE TRACKED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL

此输出(在PHP5.6.29中)

= MANUAL TRACKED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL

选项2:

确保重置$position {(1}}有效()$myIterator->valid()' when the pointer is out of bounds. (This should be done after the call to NULL中的you could check on next, by setting the pointer to我们至少可以模拟使用本机指针跟踪获得的行为。

class myIterator implements Iterator {
    private $position = 0;
    private $array = array(
        "firstelement",
        "secondelement",
        "lastelement",
    );  

    public function __construct() {
        $this->position = 0;
    }

    public function rewind() {
        $this->position = 0;
    }

    public function current() {
        return $this->array[$this->position];
    }

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

    public function next() {
        ++$this->position;
    }

    public function valid() {
        $valid = isset($this->array[$this->position]);

        if(!$valid){
            $this->position = null;
        }

        return $valid;
    }

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

}

echo str_pad('= MANUAL TRACKED POINTER =', 60, '=') ."\n";

$it = new myIterator;

$it->next(); //move to index 1

var_dump($it->key());   //<-- should print 1

foreach($it as $key => $value) {}

var_dump($it->key()); //<--shows NULL (PHP < 7),

echo str_pad('= NATIVE ARRAY POINTER =', 60, '=') ."\n";

$array = array(
    "firstelement",
    "secondelement",
    "lastelement",
 );  

 next($array); //move to index 1

var_dump(key($array)); //<-- should print 1
foreach( $array as $key => $value ){}
var_dump(key($array));  //<--  NULL (PHP < 7),  1 PHP 7+

对于Option2,我将其设置为反映PHP的本机行为&lt; 7,因为你手动跟踪数组指针,所以只用Iterator接口就没有简单的方法。

http://sandbox.onlinephpfunctions.com/code/2d38af15e5b0269dcdd341d0a77b601ce2713cce

此输出(在PHP7.0.1中)

= MANUAL TRACKED POINTER ===================================
int(1)
int(0)
= NATIVE ARRAY POINTER =====================================
int(1)
int(1)

此输出(在PHP5.6.29中)

= MANUAL TRACKED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL

<强>更新

现在,如果你想完全模仿PHP7的行为,它将需要做更多的工作。 (我相信可能有其他方法可以做到这一点,但这就是我提出的方式)

class myIterator implements IteratorAggregate{
    protected $innerIterator = [];

    public function __construct(array $array = []){
        $this->innerIterator = new myInnerIterator($array,$this);
    }

    public function getIterator(){
        $this->innerIterator->cachePointer();
        return $this->innerIterator;
    }

    public function seek($index){
        $this->innerIterator->seek($index);
    }

    public function rewind() {
        $this->innerIterator->rewind();
    }

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

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

    public function next() {
       $this->innerIterator->next();
    }

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

class myInnerIterator implements SeekableIterator{

    protected $array;

    protected $pointer_cache = null;

    public function __construct( array $array = [], $wrapper){
        if( !is_a($wrapper, 'myIterator') ) throw new Exception('myInnerIterator can only be constructed by myIterator');
        $this->array = new ArrayIterator($array);
    }

    public function cachePointer(){
        $this->pointer_cache = $this->key();
    }

    public function seek($index){
        $this->array->seek($index);
    }

    public function rewind() {
        $this->array->rewind();
    }

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

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

    public function next() {
       $this->array->next();
    }

    public function valid() {
        $valid = $this->array->valid();
        if(!$valid && $this->pointer_cache ){
            if( defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000 )
                $this->seek( $this->pointer_cache );
            $this->pointer_cache = null;
        }
        return $valid;
    }

}

echo str_pad('= CACHED  POINTER =', 60, '=') ."\n";

$array = array(
    "firstelement",
    "secondelement",
    "lastelement",
); 

$it = new myIterator($array);

$it->next(); //move to index 1

var_dump($it->key());   //<-- should print 1

foreach($it as $key => $value) {}

var_dump($it->key()); //<--shows NULL,

echo str_pad('= NATIVE ARRAY POINTER =', 60, '=') ."\n";

 next($array); //move to index 1

var_dump(key($array)); //<-- should print 1
foreach( $array as $key => $value ){}
var_dump(key($array));  //<--  NULL (PHP < 7),  1 PHP 7+

并输出

此输出(在PHP7.0.1中)

= CACHED POINTER ===================================
int(1)
int(1)
= NATIVE ARRAY POINTER =====================================
int(1)
int(1)

此输出(在PHP5.6.29中)

= CACHED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL

这是一个沙盒,您可以在其中测试

http://sandbox.onlinephpfunctions.com/code/66dc26003347ec40184bb0283e78a3d67e86c51a

设置方式你永远不必触及myInnerIterator类,因为它包含在myIterator中,实际上如果你试图创建它而不传递myIterator的实例,它会抛出异常。诀窍是你需要IteratorAggregate::getIteratorforeach开始时调用,然后SeekableIterator::seek,也必须在InnerIterator中完成位置的缓存。至于其他什么地方自动调用getIterator以及所有会影响我的东西,我真的不知道。

无论如何希望有所帮助,或者给你一些想法。