【问题标题】:PHP - keep internal pointer after foreach() on Iterator objectPHP - 在迭代器对象上的 foreach() 之后保留内部指针
【发布时间】:2018-04-15 21:47:04
【问题描述】:

以下是 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() 文档的建议相矛盾。

【问题讨论】:

    标签: php arrays foreach iterator


    【解决方案1】:

    循环后您对 Itterator 的实现中的指针不正确,因为您没有设置/重置从 foreach 循环最后一次调用 valid() 的值,它保留了数组外部的最后一个增量值边界(在本例中为 3 ),这里最简单的做法是设置指针 null 以便它反映 PHP 的行为(在迭代器中使用本机数组函数时会得到什么,稍后会详细介绍)

    有一点需要注意,正如您指出的那样,PHP7 发生了变化

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

    我个人认为这不是一个大问题,除非您在内部指针上进行中继。以我的经验,这不会发生太多。

    现在修复您的迭代器实现:

    选项 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:

    确保在$myIterator-&gt;valid()' when the pointer is out of bounds. (This should be done after the call tovalid()you could check on next, by setting the pointer toNULL`中重置$position,我们至少可以模拟使用本机指针跟踪时的行为)

    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 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::getIterator,它在foreach 启动时调用,然后是SeekableIterator::seek,位置的缓存也必须在InnerIterator 中完成。至于还有什么会自动调用getIterator 以及所有这些会影响什么,我真的不知道。

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

    【讨论】:

    • 谢谢。我对“在 PHP 7 中,foreach 不使用内部数组指针”的说法感到困惑。
    • 我对添加可搜索毫无疑问,令我烦恼的是我(以及从事该项目的每个人)需要记住在 foreach() 之前/之后保存和恢复位置 - 这将成为每个人的陷阱。我想知道为什么 PHP 没有将它集成到 foreach() 中,以便它会像承诺的那样改变内部指针。
    • 实际上在 7 中指针停留在0,你的移动是因为你在增加它。基本上你自己跟踪内部指针,这就是为什么我使用数组函数next($array)key($array) 或使用ArrayIterator 不要自己跟踪它们
    • @LazNiko - 你应该对此有疑问。那么你可能不会有奇怪的行为。除非我误解了
    • 如果不跟踪 $this->position,你将如何实现 valid()?它应该告诉当前位置是否有效。 public function valid() { return isset($this->array[$this->position]); }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-18
    • 1970-01-01
    • 1970-01-01
    • 2015-08-27
    • 2014-12-24
    • 2016-12-04
    • 1970-01-01
    相关资源
    最近更新 更多