2010-08-04 93 views
4
<? foreach ($this->criteria as $key => $value): ?> 
<li><?= $this->accommodationsLink($this->criteria, $key) ?></li> 
<? endforeach ?> 

此代碼給出意外的結果,因爲只有一個鏈接可見。但$ this->條件中有兩個項目。與迭代器接口嵌套的foreach

我探索了問題的原因。在功能accommodationsLink是另一個foreach循環工作在相同的條件對象

foreach ($criteria as $key => $value) { 
    $params[$key] = $value; 
} 

$這個 - >標準和$標準是實現了PHP的Iterator接口相同的對象。有沒有簡單的方法讓這段代碼工作或嵌套foreach循環不可能與PHP迭代器接口?

回答

2

那麼,第二個foreach將在運行之前調用$iterator->reset()。因此,當第二的foreach達到迭代結束,內部指針已經處於數組的結尾......

它會像:

$it->reset(); 
while ($it->valid()) { 
    $it->reset(); 
    while ($it->valid()) { 
     //do something 
     $it->next(); 
    } 
    $it->next(); 
} 

購買它到達的時間$it->next()在外部循環中調用,它已經無效。所以next()調用將「失敗」,$it->valid()將返回false。

這不是迭代器的問題,這是您使用的邏輯的問題。如果你真的必須嵌套循環,然後clone迭代器($subit = clone $it)在內部循環,所以你不要打擾指針...

編輯:例如用克隆:

$it->reset(); 
while ($it->valid()) { 
    $bar = clone $it; 
    $bar->reset(); 
    while ($bar->valid()) { 
     //do something 
     $bar->next(); 
    } 
    $it->next(); 
} 

或者,使用foreach(這在語義上是等效的):

foreach ($it as $key => $value) { 
    $subit = clone $it; 
    foreach ($subit as $k => $v) { 
     //Do stuff 
    } 
} 
+4

另一種方法是實施['IteratorAggregate'](http://www.php.net/manual/en/class.iteratoraggregate.php)來代替。如果碰巧實現迭代器的對象封裝大量數據,克隆可能會很昂貴。 – Artefacto 2010-08-04 12:52:29

+1

@Artee你應該根據你的評論寫一個答案。 – 2014-02-14 21:12:12

1

我試過這與普通數組和PHP迭代器。不幸的是PHP迭代器,因爲它們是對象,工作方式不同。對象通過引用傳遞,而數組是按值傳遞的。所以當嵌套的foreach到達迭代器的末尾時,第一個foreach不能恢復到它停止的地方,因爲內部指針被設置爲最後一個元素。

看看下面的例子使用純PHP陣列寫:

$test = [1, 2, 3]; 

foreach ($test as $i1 => $v1) { 
    echo "first loop: $i1\n"; 

    foreach ($test as $i2 => $v2) { 
     echo "second loop: $i2\n"; 
    } 
} 

上面的代碼產生以下輸出:

first loop: 0 
second loop: 0 
second loop: 1 
second loop: 2 
first loop: 1 
second loop: 0 
second loop: 1 
second loop: 2 
first loop: 2 
second loop: 0 
second loop: 1 
second loop: 2 

如果我們嘗試用一個迭代器同樣的事情,我們得到相當不同的結果。爲了避免混淆,我將使用ArrayIterator類,以便所有東西都已經由PHP人員實現,並且我們不會以錯誤的方式使用接口。所以容不得這裏的錯誤,這是迭代器是如何通過它們來實現:

$test = new ArrayIterator([1, 2, 3]); 

foreach ($test as $i1 => $v1) { 
    echo "first loop: $i1\n"; 

    foreach ($test as $i2 => $v2) { 
     echo "second loop: $i2\n"; 
    } 
} 

輸出是:

first loop: 0 
second loop: 0 
second loop: 1 
second loop: 2 

正如你所看到的第一的foreach被執行一次。

解決方法可能是實施SeekableIterator接口。它會讓我們使用seek()方法將內部指針重置爲其正確的值。在我看來這是一個不好的做法,但如果PHP的人不解決這個問題,我不能說它是最好的。我從現在開始可能會避免使用迭代器,因爲它們看起來與陣列的行爲不同,我認爲這是人們首先假設的。因此,使用它們會使我的應用程序出錯,因爲可能是因爲我的團隊中的開發人員不知道這一點,並且隨代碼混淆。

遵循一個例子與SeekableIterator接口:

class MyIterator implements SeekableIterator 
{ 
    private $position = 0; 
    private $array = [1, 2, 3]; 

    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() 
    { 
     return isset($this->array[$this->position]); 
    } 

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

$test = new MyIterator(); 

foreach ($test as $i1 => $v1) { 
    echo "first loop $i1\n"; 

    foreach ($test as $i2 => $v2) { 
     echo "second loop $i2\n"; 
    } 

    $test->seek($i1); 
} 

輸出是任何人會想到:這一切

first loop: 0 
second loop: 0 
second loop: 1 
second loop: 2 
first loop: 1 
second loop: 0 
second loop: 1 
second loop: 2 
first loop: 2 
second loop: 0 
second loop: 1 
second loop: 2 

是因爲每個的foreach工作在自己的數組的副本。迭代器,因爲它們是對象,通過引用傳遞。因此,每個foreach共享同一個對象。如果嘗試在嵌套的foreach中取消設置元素,也會發生同樣的情況。未設置將增加內部指針。然後執行到達嵌套foreach的末尾,內部指針再次增加。這意味着,如果未設置,我們會增加內部指針兩次。父親的foreach因此會跳過一個元素。

我的建議是,如果你不能避免迭代器,真的很小心。始終對它們進行單元測試。

注意:在PHP 5.6.14和PHP 7.0.0 RC5上測試的代碼。

0

編輯: 發佈此之後,我意識到,如果你在嵌套的foreach做continuebreak,否則會破壞嚴重。所以這可能不是你想要的解決方案。

在其他的答案PHP的foreach表示在每次迭代結束時foreach環和valid的開始調用rewind。所以在嵌套的foreach迭代器變得無效並且在父親foreach中保持這種方式。這裏是一個令人討厭的解決方法,它使用堆棧指針而不是單個指針,並使得這個迭代器在這種情況下表現得像數組一樣。

class Test implements Iterator { 
    private $loopstack = []; 

    private $array = array("A", "B", "C",); 

    function rewind() { 
     $this->loopstack[] = 0; 
    } 

    function current() { 
     return $this->array[end($this->loopstack)]; 
    } 

    function key() { 
     return end($this->loopstack); 
    } 

    function next() { 
     array_push($this->loopstack, array_pop($this->loopstack) + 1); 
    } 

    function valid() { 
     $valid = isset($this->array[end($this->loopstack)]); 
     if (!$valid) { 
      array_pop($this->loopstack); 
     } 
     return $valid; 
    } 
} 

$iterator = new Test(); 
foreach ($iterator as $e){ 
    var_dump('loop1 ' . $e); 
    foreach ($iterator as $e2){ 
     var_dump('loop2 ' . $e2); 
    } 
} 

輸出:

string(7) "loop1 A" 
string(7) "loop2 A" 
string(7) "loop2 B" 
string(7) "loop2 C" 
string(7) "loop1 B" 
string(7) "loop2 A" 
string(7) "loop2 B" 
string(7) "loop2 C" 
string(7) "loop1 C" 
string(7) "loop2 A" 
string(7) "loop2 B" 
string(7) "loop2 C"