with - php inline for loop




In che modo PHP "foreach" funziona davvero? (5)

Lasciatemi prefisso dicendo che so cosa è, cosa fa e come usarlo. Questa domanda riguarda il modo in cui funziona sotto il cofano e non voglio alcuna risposta sulla falsariga di "questo è il modo in cui si esegue il ciclo di un array con foreach ".

Per molto tempo ho pensato che foreach funzionasse con l'array stesso. Poi ho trovato molti riferimenti al fatto che funziona con una copia dell'array, e da allora ho assunto che questa fosse la fine della storia. Ma di recente ho discusso la questione, e dopo un po 'di sperimentazione ho scoperto che non era vero al 100%.

Lascia che mostri cosa intendo. Per i seguenti casi di test, lavoreremo con il seguente array:

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

Test case 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 */

Questo dimostra chiaramente che non stiamo lavorando direttamente con l'array sorgente, altrimenti il ​​ciclo continuerebbe per sempre, dal momento che stiamo costantemente spingendo gli elementi sull'array durante il ciclo. Ma solo per essere sicuri che questo è il caso:

Test case 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 */

Ciò conferma la nostra conclusione iniziale, stiamo lavorando con una copia dell'array sorgente durante il ciclo, altrimenti vedremmo i valori modificati durante il ciclo. Ma...

Se guardiamo nel manual , troviamo questa affermazione:

Quando foreach viene avviato per la prima volta, il puntatore dell'array interno viene automaticamente reimpostato sul primo elemento dell'array.

Giusto ... questo sembra suggerire che foreach si basa sul puntatore dell'array dell'array sorgente. Ma abbiamo appena dimostrato che non stiamo lavorando con l'array sorgente , giusto? Bene, non interamente.

Test case 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)
*/

Quindi, nonostante non lavoriamo direttamente con l'array sorgente, stiamo lavorando direttamente con il puntatore dell'array sorgente: il fatto che il puntatore si trovi alla fine dell'array alla fine del loop lo mostra. Tranne che questo non può essere vero - se lo fosse, allora il test case 1 sarebbe in loop per sempre.

Il manuale PHP afferma inoltre:

Siccome foreach si basa sul puntatore dell'array interno cambiandolo all'interno del loop può portare a comportamenti imprevisti.

Bene, scopriamo cos'è questo "comportamento inaspettato" (tecnicamente, qualsiasi comportamento è inaspettato poiché non so più cosa aspettarmi).

Test case 4 :

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

/* Output: 1 2 3 4 5 */

Test case 5 :

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

/* Output: 1 2 3 4 5 */

... niente di così inaspettato, in effetti sembra supportare la teoria della "copia della fonte".

La domanda

Che cosa sta succedendo qui? Il mio C-fu non è abbastanza buono per me da poter estrarre una conclusione corretta semplicemente guardando il codice sorgente PHP, sarei grato se qualcuno potesse tradurlo in inglese per me.

Mi sembra che foreach con una copia dell'array, ma imposta il puntatore dell'array dell'array sorgente alla fine dell'array dopo il ciclo.

  • È corretto e l'intera storia?
  • Se no, cosa sta facendo davvero?
  • C'è qualche situazione in cui l'uso di funzioni che regolano il puntatore dell'array ( each() , reset() et al.) Durante un foreach potrebbe influenzare il risultato del loop?

Alcuni punti da notare quando si lavora con foreach() :

a) foreach funziona sulla copia prospettica dell'array originale. Significa che foreach () avrà CONDIVISO la memorizzazione dei dati fino a quando o meno una prospected copy non viene creata per manual .

b) Cosa fa scattare una copia prospettica ? La copia prospettica viene creata in base alla politica di copy-on-write , ovvero ogni volta che viene modificato un array passato a foreach (), viene creato un clone dell'array originale.

c) L'array originale e l'iteratore foreach () avranno DISTINCT SENTINEL VARIABLES , cioè uno per l'array originale e l'altro per foreach; guarda il codice di prova qui sotto. SPL , Iterators e Array Iterator .

question Come assicurarsi che il valore sia resettato in un ciclo 'foreach' in PHP? affronta i casi (3,4,5) della tua domanda.

L'esempio seguente mostra che each () e reset () NON influiscono sulle variabili SENTINEL (for example, the current index variable) dell'iteratore 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/>";

Produzione:

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

Nell'esempio 3 non si modifica la matrice. In tutti gli altri esempi si modifica il contenuto o il puntatore dell'array interno. Questo è importante quando si tratta di array PHP causa della semantica dell'operatore di assegnazione.

L'operatore di assegnazione per gli array in PHP funziona più come un clone pigro. Assegnare una variabile a un'altra che contiene una matrice clonerà la matrice, a differenza della maggior parte delle lingue. Tuttavia, la clonazione effettiva non verrà eseguita a meno che non sia necessaria. Ciò significa che il clone avverrà solo quando una delle variabili viene modificata (copy-on-write).

Ecco un esempio:

$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.

Tornando ai tuoi casi di test, puoi facilmente immaginare che foreach crei una sorta di iteratore con un riferimento alla matrice. Questo riferimento funziona esattamente come la variabile $b nel mio esempio. Tuttavia, l'iteratore e il riferimento vengono visualizzati solo durante il ciclo e quindi vengono entrambi scartati. Ora puoi vedere che, in tutti i casi tranne 3, la matrice viene modificata durante il ciclo, mentre questo riferimento extra è vivo. Questo fa scattare un clone e questo spiega cosa sta succedendo qui!

Ecco un eccellente articolo per un altro effetto collaterale di questo comportamento copy-on-write: The PHP Ternary Operator: Fast or not?


NOTA PER PHP 7

Per aggiornare questa risposta in quanto ha acquisito una certa popolarità: questa risposta non si applica più a partire da PHP 7. Come spiegato nella sezione " Modifiche all'indietro incompatibili ", in PHP 7 foreach funziona sulla copia dell'array, quindi qualsiasi modifica sullo stesso array non si riflettono sul ciclo foreach. Maggiori dettagli al link

Spiegazione (citazione da php.net ):

Il primo modulo esegue il loop sull'array dato da array_expression. Ad ogni iterazione, il valore dell'elemento corrente viene assegnato a $ value e il puntatore dell'array interno viene avanzato di uno (quindi nella prossima iterazione, si guarderà al prossimo elemento).

Quindi, nel tuo primo esempio hai solo un elemento nell'array, e quando il puntatore viene spostato, l'elemento successivo non esiste, quindi dopo aver aggiunto il nuovo elemento foreach finisce perché già "decide" che è l'ultimo elemento.

Nel secondo esempio, si inizia con due elementi e il ciclo foreach non si trova nell'ultimo elemento, quindi valuta l'array alla successiva iterazione e quindi si rende conto che c'è un nuovo elemento nell'array.

Credo che questa sia una conseguenza di ciascuna parte della spiegazione nella documentazione, il che probabilmente significa che foreachfa tutta la logica prima di chiamare il codice {}.

Test case

Se esegui questo:

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

Otterrai questo risultato:

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

Il che significa che ha accettato la modifica e l'ha esaminata perché è stata modificata "in tempo". Ma se lo fai:

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

Otterrete:

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

Il che significa che l'array è stato modificato, ma da quando lo abbiamo modificato quando foreachera già nell'ultimo elemento dell'array, "ha deciso" di non effettuare più il ciclo, e anche se abbiamo aggiunto un nuovo elemento, lo abbiamo aggiunto "troppo tardi" e non è stato collegato.

Una spiegazione dettagliata può essere letta in Come funziona effettivamente PHP foreach? che spiega gli interni di questo comportamento.


Secondo la documentazione fornita dal manuale PHP.

Ad ogni iterazione, il valore dell'elemento corrente viene assegnato a $ v e il
puntatore dell'array interno viene avanzato di uno (quindi nella prossima iterazione, si guarderà al prossimo elemento).

Quindi, come per il tuo primo esempio:

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

$arrayha solo un singolo elemento, così come per l'esecuzione foreach, 1 assegna a $ve non ha nessun altro elemento per spostare il puntatore

Ma nel tuo secondo esempio:

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

$arrayhanno due elementi, quindi ora $ array valuta gli indici zero e sposta il puntatore di uno. Per prima iterazione del ciclo, aggiunto $array['baz']=3;come passaggio per riferimento.


Il ciclo foreach di PHP può essere usato con Indexed arrays, Associative arrayse Object public variables.

Nel ciclo foreach, la prima cosa che fa php è che crea una copia dell'array su cui deve essere ripetuta l'iterazione. PHP quindi itera su questo nuovo copyarray piuttosto che su quello originale. Questo è dimostrato nell'esempio seguente:

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

Oltre a questo, php consente di utilizzare iterated values as a reference to the original array valueanche. Questo è dimostrato di seguito:

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

Nota: Non permette original array indexesdi essere utilizzato come references.

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





php-internals