net - Comment fonctionne PHP 'foreach'?




php boucle for next (5)

Permettez-moi de préfixer cela en disant que je sais ce qu'est foreach , fait et comment l'utiliser. Cette question concerne comment cela fonctionne sous le capot, et je ne veux pas de réponses dans le sens de "voici comment vous bouclez un tableau avec foreach ".

Pendant longtemps, j'ai supposé que foreach travaillait avec le tableau lui-même. Ensuite, j'ai trouvé de nombreuses références au fait que cela fonctionne avec une copie du tableau, et j'ai depuis supposé que c'était la fin de l'histoire. Mais j'ai récemment entamé une discussion à ce sujet, et après un peu d'expérimentation j'ai découvert que ce n'était pas vrai à 100%.

Laisse-moi te montrer ce que je veux dire. Pour les cas de test suivants, nous allons travailler avec le tableau suivant:

$array = array(1, 2, 3, 4, 5);

Cas de test 1 :

foreach ($array as $item) {
  echo "$item\n";
  $array[] = $item;
}
print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 2 3 4 5 1 2 3 4 5 */

Cela montre clairement que nous ne travaillons pas directement avec le tableau source - sinon la boucle continuerait indéfiniment, car nous poussons constamment des éléments sur le tableau pendant la boucle. Mais juste pour être sûr que c'est le cas:

Cas de test 2 :

foreach ($array as $key => $item) {
  $array[$key + 1] = $item + 2;
  echo "$item\n";
}

print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 3 4 5 6 7 */

Cela sauvegarde notre conclusion initiale, nous travaillons avec une copie du tableau source pendant la boucle, sinon nous verrons les valeurs modifiées pendant la boucle. Mais...

Si nous regardons dans le manual , nous trouvons cette déclaration:

Lorsque foreach commence à s'exécuter, le pointeur interne du tableau est automatiquement réinitialisé sur le premier élément du tableau.

À droite ... cela semble indiquer que foreach s'appuie sur le pointeur de tableau du tableau source. Mais nous avons juste prouvé que nous ne travaillons pas avec le tableau source , n'est-ce pas? Eh bien, pas entièrement.

Cas de test 3 :

// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));

foreach ($array as $item) {
  echo "$item\n";
}

var_dump(each($array));

/* Output
  array(4) {
    [1]=>
    int(1)
    ["value"]=>
    int(1)
    [0]=>
    int(0)
    ["key"]=>
    int(0)
  }
  1
  2
  3
  4
  5
  bool(false)
*/

Ainsi, malgré le fait que nous ne travaillons pas directement avec le tableau source, nous travaillons directement avec le pointeur du tableau source - le fait que le pointeur se trouve à la fin du tableau à la fin de la boucle le montre. Sauf que cela ne peut pas être vrai - si c'était le cas, alors le cas de test 1 serait bouclé pour toujours.

Le manuel de PHP indique également:

Comme foreach s'appuie sur le pointeur de tableau interne en le modifiant dans la boucle peut conduire à un comportement inattendu.

Eh bien, découvrons ce qu'est ce "comportement inattendu" (techniquement, tout comportement est inattendu puisque je ne sais plus à quoi m'attendre).

Cas de test 4 :

foreach ($array as $key => $item) {
  echo "$item\n";
  each($array);
}

/* Output: 1 2 3 4 5 */

Cas de test 5 :

foreach ($array as $key => $item) {
  echo "$item\n";
  reset($array);
}

/* Output: 1 2 3 4 5 */

... rien d'inattendu là-dedans, en fait il semble soutenir la théorie de la "copie de la source".

La question

Qu'est-ce qui se passe ici? Mon C-fu n'est pas assez bon pour que je puisse extraire une bonne conclusion simplement en regardant le code source de PHP, j'apprécierais que quelqu'un puisse le traduire en anglais pour moi.

Il me semble que foreach fonctionne avec une copie du tableau, mais définit le pointeur de tableau du tableau source à la fin du tableau après la boucle.

  • Est-ce correct et toute l'histoire?
  • Si non, que fait-il vraiment?
  • Y at-il une situation où l'utilisation de fonctions qui ajustent le pointeur de tableau ( each() , reset() et al.) Pendant une foreach pourrait affecter le résultat de la boucle?

https://code.i-harness.com


Dans l'exemple 3, vous ne modifiez pas le tableau. Dans tous les autres exemples, vous modifiez le contenu ou le pointeur de tableau interne. Ceci est important quand il s'agit de tableaux PHP en raison de la sémantique de l'opérateur d'affectation.

L'opérateur d'affectation pour les tableaux en PHP fonctionne plus comme un clone paresseux. Assigner une variable à une autre qui contient un tableau clone le tableau, contrairement à la plupart des langues. Cependant, le clonage réel ne sera pas fait à moins que ce soit nécessaire. Cela signifie que le clone n'aura lieu que lorsque l'une des variables est modifiée (copie sur écriture).

Voici un exemple:

$a = array(1,2,3);
$b = $a;  // This is lazy cloning of $a. For the time
          // being $a and $b point to the same internal
          // data structure.

$a[] = 3; // Here $a changes, which triggers the actual
          // cloning. From now on, $a and $b are two
          // different data structures. The same would
          // happen if there were a change in $b.

Pour revenir à vos cas de test, vous pouvez facilement imaginer que foreach crée une sorte d'itérateur avec une référence au tableau. Cette référence fonctionne exactement comme la variable $b dans mon exemple. Cependant, l'itérateur avec la référence ne vit que pendant la boucle et ensuite, ils sont tous deux mis au rebut. Maintenant, vous pouvez voir que, dans tous les cas sauf 3, le tableau est modifié pendant la boucle, alors que cette référence supplémentaire est vivante. Cela déclenche un clone, et cela explique ce qui se passe ici!

Voici un excellent article pour un autre effet secondaire de ce comportement de copie-écriture: L'opérateur ternaire PHP: Rapide ou non?


Quelques points à noter lorsque vous travaillez avec foreach() :

a) foreach travaille sur la copie prospectée du tableau original. Cela signifie que foreach () aura le stockage de données SHARED jusqu'à ou à moins qu'une prospected copy ne soit pas créée pour manual .

b) Qu'est-ce qui déclenche une copie prospectée ? La copie prospecte est créée sur la base de la politique de copy-on-write , c'est copy-on-write dire qu'à chaque fois qu'un tableau passé à foreach () est modifié, un clone du tableau original est créé.

c) Le tableau original et l'itérateur foreach () auront des DISTINCT SENTINEL VARIABLES , c'est-à-dire, une pour le tableau original et l'autre pour foreach; voir le code de test ci-dessous. SPL , Iterators et Array Iterator .

question Comment s'assurer que la valeur est réinitialisée dans une boucle 'foreach' en PHP? traite les cas (3,4,5) de votre question.

L'exemple suivant montre que each () et reset () n'affectent PAS les variables SENTINEL (for example, the current index variable) de l'itérateur foreach ().

$array = array(1, 2, 3, 4, 5);

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

foreach($array as $key => $val){
    echo "foreach: $key => $val<br/>";

    list($key2,$val2) = each($array);
    echo "each() Original(inside): $key2 => $val2<br/>";

    echo "--------Iteration--------<br/>";
    if ($key == 3){
        echo "Resetting original array pointer<br/>";
        reset($array);
    }
}

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

Sortie:

each() Original (outside): 0 => 1
foreach: 0 => 1
each() Original(inside): 1 => 2
--------Iteration--------
foreach: 1 => 2
each() Original(inside): 2 => 3
--------Iteration--------
foreach: 2 => 3
each() Original(inside): 3 => 4
--------Iteration--------
foreach: 3 => 4
each() Original(inside): 4 => 5
--------Iteration--------
Resetting original array pointer
foreach: 4 => 5
each() Original(inside): 0=>1
--------Iteration--------
each() Original (outside): 1 => 2

Explication (citation de php.net ):

La première forme boucle sur le tableau donné par array_expression. A chaque itération, la valeur de l'élément en cours est assignée à $ value et le pointeur interne du tableau est avancé de un (donc à l'itération suivante, vous regarderez l'élément suivant).

Ainsi, dans votre premier exemple, vous n'avez qu'un élément dans le tableau, et lorsque le pointeur est déplacé, l'élément suivant n'existe pas, donc après avoir ajouté un nouvel élément pour chaque extrémité, car il l'a déjà "décidé" comme dernier élément.

Dans votre deuxième exemple, vous commencez avec deux éléments, et foreach loop n'est pas au dernier élément, donc il évalue le tableau à l'itération suivante et réalise ainsi qu'il y a un nouvel élément dans le tableau.

Je crois que ceci est la conséquence de chaque partie d' itération de l'explication dans la documentation, ce qui signifie probablement que foreach fait toute la logique avant d'appeler le code dans {} .

Cas de test

Si vous exécutez ceci:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        $array['baz']=3;
        echo $v." ";
    }
    print_r($array);
?>

Vous obtiendrez cette sortie:

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

Ce qui signifie qu'il a accepté la modification et l'a traversée parce qu'elle a été modifiée "à temps". Mais si vous faites ceci:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        if ($k=='bar') {
            $array['baz']=3;
        }
        echo $v." ";
    }
    print_r($array);
?>

Tu auras:

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

Which means that array was modified, but since we modified it when the foreach already was at the last element of the array, it "decided" not to loop anymore, and even though we added new element, we added it "too late" and it was not looped through.

Detailed explanation can be read at How does PHP 'foreach' actually work? which explains the internals behind this behaviour.


As per the documentation provided by PHP manual.

On each iteration, the value of the current element is assigned to $v and the internal
array pointer is advanced by one (so on the next iteration, you'll be looking at the next element).

So as per your first example:

$array = ['foo'=>1];
foreach($array as $k=>&$v)
{
   $array['bar']=2;
   echo($v);
}

$array have only single element, so as per the foreach execution, 1 assign to $v and it don't have any other element to move pointer

But in your second example:

$array = ['foo'=>1, 'bar'=>2];
foreach($array as $k=>&$v)
{
   $array['baz']=3;
   echo($v);
}

$array have two element, so now $array evaluate the zero indices and move the pointer by one. For first iteration of loop, added $array['baz']=3; as pass by reference.


PHP foreach loop can be used with Indexed arrays , Associative arrays and Object public variables .

In foreach loop, the first thing php does is that it creates a copy of the array which is to be iterated over. PHP then iterates over this new copy of the array rather than the original one. This is demonstrated in the below example:

<?php
$numbers = [1,2,3,4,5,6,7,8,9]; # initial values for our array
echo '<pre>', print_r($numbers, true), '</pre>', '<hr />';
foreach($numbers as $index => $number){
    $numbers[$index] = $number + 1; # this is making changes to the origial array
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # showing data from the copied array
}
echo '<hr />', '<pre>', print_r($numbers, true), '</pre>'; # shows the original values (also includes the newly added values).

Besides this, php does allow to use iterated values as a reference to the original array value as well. This is demonstrated below:

<?php
$numbers = [1,2,3,4,5,6,7,8,9];
echo '<pre>', print_r($numbers, true), '</pre>';
foreach($numbers as $index => &$number){
    ++$number; # we are incrementing the original value
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # this is showing the original value
}
echo '<hr />';
echo '<pre>', print_r($numbers, true), '</pre>'; # we are again showing the original value

Note: It does not allow original array indexes to be used as references .

Source: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples





php-internals