web-dev-qa-db-fra.com

PHP Foreach Pass by Reference: Duplication du dernier élément? (Punaise?)

J'ai juste eu un comportement très étrange avec un script php simple que j'écrivais. Je l'ai réduit au minimum nécessaire pour recréer le bogue:

<?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?

?>

Cela génère:

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

Est-ce un bug ou un comportement vraiment étrange qui est censé se produire?

154
regality

Après la première boucle foreach, $item est toujours une référence à une valeur qui est également utilisée par $arr[2]. Ainsi, chaque appel foreach dans la deuxième boucle, qui n'appelle pas par référence, remplace cette valeur, et donc $arr[2], par la nouvelle valeur.

Donc, la boucle 1, la valeur et $arr[2] deviennent $arr[0], qui est 'foo'.
En boucle 2, la valeur et $arr[2] deviennent $arr[1], ce qui correspond à 'bar'.
Boucle 3, la valeur et $arr[2] deviennent $arr[2], qui est «bar» (à cause de la boucle 2).

La valeur "baz" est perdue lors du premier appel de la deuxième boucle foreach.

Débogage de la sortie

Pour chaque itération de la boucle, nous allons faire écho à la valeur de $item et imprimer récursivement le tableau $arr.

Lorsque la première boucle est exécutée, nous voyons cette sortie:

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

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

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

À la fin de la boucle, $item pointe toujours au même endroit que $arr[2].

Lorsque la deuxième boucle est exécutée, nous voyons cette sortie:

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

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

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

Vous remarquerez que chaque fois que le tableau insère une nouvelle valeur dans $item, il met également à jour $arr[3] avec la même valeur, car ils pointent toujours vers le même emplacement. Lorsque la boucle atteint la troisième valeur du tableau, elle contient la valeur bar car elle vient d'être définie par l'itération précédente de cette boucle.

Est-ce un bug?

Non, c'est le comportement d'un élément référencé et non un bogue. Ce serait comme exécuter quelque chose comme:

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

Une boucle foreach n’a pas de nature particulière dans laquelle elle peut ignorer les éléments référencés. Il suffit de définir cette variable sur la nouvelle valeur chaque fois que vous le feriez en dehors d'une boucle.

166
animuson

$item est une référence à $arr[2] et est écrasée par la deuxième boucle foreach, comme l'a souligné animuson.

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

Bien que cela puisse ne pas être officiellement un bogue, à mon avis, il l’est. Je pense que le problème ici est que nous nous attendons à ce que $item disparaisse de la portée lors de la sortie de la boucle, comme dans beaucoup d'autres langages de programmation. Cependant, cela ne semble pas être le cas ...

Ce code ...

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

Donne la sortie ...

one
two
three
three

Comme d'autres personnes l'ont déjà dit, vous écrasez la variable référencée dans $arr[2] avec votre deuxième boucle, mais cela ne se produit que parce que $item n'a jamais été hors de portée. Qu'est-ce que vous en pensez ... bug?

3
jocull

c'est parce que vous utilisez la directive de référence (&). la dernière valeur sera remplacée par la deuxième boucle et cela corrompra votre tableau . la solution la plus simple consiste à utiliser un nom différent pour la deuxième boucle

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

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

Le comportement correct de PHP devrait être une erreur NOTICE dans mon opinion . Si une variable référencée créée dans une boucle foreach est utilisée en dehors de la boucle, elle devrait provoquer un avis . Très facile de tomber pour cela comportement, très difficile à repérer quand cela s’est passé ... Et aucun développeur ne lira la page de documentation de foreach, ce n’est pas une aide.

Vous devriez unset() la référence après votre boucle pour éviter ce genre de problème . Unset () sur une référence supprimera simplement la référence sans endommager les données d'origine.

0
John

Une explication plus facile semble de Rasmus Lerdorf, créateur original de PHP: https://bugs.php.net/bug.php?id=71454

0
qdinar