arrays loop - PHP Foreach Pass per riferimento:l'ultimo elemento duplicato?(Bug?)




associative next (6)

Una spiegazione più semplice, sembra da Rasmus Lerdorf, creatore originale di PHP: https://bugs.php.net/bug.php?id=71454

Ho appena avuto un comportamento molto strano con un semplice script php che stavo scrivendo. L'ho ridotto al minimo necessario per ricreare il bug:

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

?>

Questo produce:

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

Si tratta di un bug o di un comportamento davvero strano che dovrebbe accadere?


$item è un riferimento a $arr[2] e viene sovrascritto dal secondo ciclo foreach come evidenziato da 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?

Il corretto comportamento di PHP potrebbe essere un errore di AVVISO nella mia opinione. Se una variabile di riferimento creata in un ciclo foreach viene utilizzata al di fuori del ciclo, dovrebbe causare un avviso. Molto facile cadere per questo comportamento, molto difficile da individuare quando è successo. E nessuno sviluppatore leggerà la pagina della documentazione di foreach, non è un aiuto.

È necessario unset() il riferimento dopo il ciclo per evitare questo tipo di problema. unset () su un riferimento rimuove solo il riferimento senza danneggiare i dati originali.


questo perché si usa la direttiva ref (&). l'ultimo valore sarà sostituito dal secondo ciclo e corromperà l'array. la soluzione più semplice è usare un nome diverso per il secondo ciclo:

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

foreach ($arr as $anotherItem) { ... }

Dopo il primo ciclo foreach, $item è ancora un riferimento ad un valore che viene utilizzato anche da $arr[2] . Quindi ogni chiamata foreach nel secondo ciclo, che non chiama per riferimento, sostituisce quel valore, e quindi $arr[2] , con il nuovo valore.

Quindi il ciclo 1, il valore e $arr[2] diventano $arr[0] , che è 'foo'.
Loop 2, il valore e $arr[2] diventano $arr[1] , che è 'bar'.
Loop 3, il valore e $arr[2] diventano $arr[2] , che è 'bar' (a causa del ciclo 2).

Il valore 'baz' è effettivamente perso alla prima chiamata del secondo ciclo foreach.

Debug dell'output

Per ogni iterazione del ciclo, faremo eco al valore di $item e stamperemo ricorsivamente l'array $arr .

Quando viene eseguito il primo ciclo, vediamo questo risultato:

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

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

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

Alla fine del ciclo, $item punta ancora allo stesso posto di $arr[2] .

Quando viene eseguito il secondo ciclo, vediamo questo risultato:

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

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

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

Noterai come ogni volta che la matrice manda un nuovo valore in $item , aggiorna anche $arr[3] con lo stesso valore, poiché entrambi stanno ancora puntando alla stessa posizione. Quando il ciclo raggiunge il terzo valore dell'array, conterrà la bar valori perché è stato appena impostato dall'iterazione precedente di quel ciclo.

E 'un errore?

No. Questo è il comportamento di un oggetto referenziato e non un bug. Sarebbe simile all'esecuzione di qualcosa come:

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

Un ciclo foreach non è speciale in natura in cui può ignorare gli elementi di riferimento. Semplicemente imposta quella variabile sul nuovo valore ogni volta come se fosse fuori dal ciclo.


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.





php arrays loops reference