web-dev-qa-db-de.com

PHP Foreach Pass by Reference: Letztes Element, das dupliziert wird? (Fehler?)

Ich hatte gerade ein seltsames Verhalten mit einem einfachen PHP-Skript, das ich schrieb. Ich habe es auf das notwendige Minimum reduziert, um den Fehler neu zu erstellen:

<?php

$arr = array("foo",
             "bar",
             "baz");

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

?>

Dies gibt aus:

Array
(
    [0] => foo
    [1] => bar
    [2] => baz
)
Array
(
    [0] => foo
    [1] => bar
    [2] => bar
)

Ist das ein Fehler oder ein wirklich seltsames Verhalten, das passieren soll?

154
regality

Nach der ersten foreach-Schleife ist $item immer noch ein Verweis auf einen Wert, der auch von $arr[2] verwendet wird. Jeder foreach-Aufruf in der zweiten Schleife, der nicht per Referenz aufgerufen wird, ersetzt also diesen Wert und damit $arr[2] durch den neuen Wert.

In Schleife 1 werden der Wert und $arr[2] zu $arr[0], was 'foo' ist.
Loop 2, der Wert und $arr[2] werden zu $arr[1], was 'bar' ist.
Loop 3, der Wert und $arr[2] werden $arr[2], was 'bar' ist (wegen Loop 2).

Der Wert 'baz' geht beim ersten Aufruf der zweiten Foreach-Schleife tatsächlich verloren.

Debuggen der Ausgabe

Für jede Wiederholung der Schleife geben wir den Wert von $item zurück und drucken rekursiv das Array $arr.

Wenn die erste Schleife durchlaufen wird, sehen wir diese Ausgabe:

foo
Array ( [0] => foo [1] => bar [2] => baz )

bar
Array ( [0] => foo [1] => bar [2] => baz )

baz
Array ( [0] => foo [1] => bar [2] => baz )

Am Ende der Schleife zeigt $item immer noch auf dieselbe Stelle wie $arr[2].

Wenn die zweite Schleife durchlaufen wird, sehen wir diese Ausgabe:

foo
Array ( [0] => foo [1] => bar [2] => foo )

bar
Array ( [0] => foo [1] => bar [2] => bar )

bar
Array ( [0] => foo [1] => bar [2] => bar )

Sie werden feststellen, wie jedes Zeitfeld einen neuen Wert in $item einfügt, und $arr[3] wurde mit demselben Wert aktualisiert, da beide immer noch auf dieselbe Position zeigen. Wenn die Schleife den dritten Wert des Arrays erreicht, enthält sie den Wert bar, da er gerade durch die vorherige Iteration dieser Schleife festgelegt wurde.

Ist es ein Fehler?

Nein. Dies ist das Verhalten eines referenzierten Elements und nicht eines Fehlers. Es wäre ähnlich zu laufen wie:

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

Eine foreach-Schleife ist nicht speziell in der Natur, in der sie referenzierte Elemente ignorieren kann. Diese Variable wird einfach jedes Mal auf den neuen Wert gesetzt, als würden Sie sich außerhalb einer Schleife befinden.

166
animuson

$item ist eine Referenz auf $arr[2] und wird von der zweiten foreach-Schleife überschrieben, wie auf animuson hingewiesen wird.

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

unset($item); // This will fix the issue.

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?
28
Michael Leaney

Dies ist zwar möglicherweise kein Fehler, aber meiner Meinung nach. Ich denke, das Problem hier ist, dass wir erwarten, dass $item den Gültigkeitsbereich verlässt, wenn die Schleife beendet wird, wie dies in vielen anderen Programmiersprachen der Fall wäre. Das scheint jedoch nicht der Fall zu sein ...

Dieser Code ...

$arr = array('one', 'two', 'three');
foreach($arr as $item){
    echo "$item\n";
}    
echo $item;

Gibt die Ausgabe aus ...

one
two
three
three

Wie andere Leute bereits sagten, überschreiben Sie die referenzierte Variable in $arr[2] mit Ihrer zweiten Schleife. Dies geschieht jedoch nur, weil $item nie außerhalb des Bereichs war. Was denkst du ... Fehler?

3
jocull

das liegt daran, dass Sie die ref-Direktive (&) verwenden. letzter Wert wird durch die zweite Schleife ersetzt und das Array wird beschädigt. Die einfachste Lösung besteht darin, einen anderen Namen für die zweite Schleife zu verwenden:

foreach ($arr as &$item) { ... }

foreach ($arr as $anotherItem) { ... }
0
Amir Surnay

Das korrekte Verhalten von PHP sollte ein NOTICE-Fehler in meiner Meinung sein. Wenn eine referenzierte Variable, die in einer foreach-Schleife erstellt wird, außerhalb der Schleife verwendet wird, sollte dies zu einer Meldung führen. Dies kann sehr leicht fallen Verhalten, sehr schwer zu erkennen, wenn es passiert ist ..__ Und kein Entwickler wird die foreach-Dokumentationsseite lesen, es ist keine Hilfe.

Sie sollten unset() die Referenz nach Ihrer Schleife verwenden, um diese Art von Problem zu vermeiden Unset () auf einer Referenz entfernt einfach die Referenz, ohne die Originaldaten zu beschädigen.

0
John

Eine einfachere Erklärung scheint Rasmus Lerdorf, ursprünglicher Schöpfer von PHP, zu sein: https://bugs.php.net/bug.php?id=71454

0
qdinar