Home > Programowanie > Referencja w pętli foreach – bug który nie jest bugiem

Referencja w pętli foreach – bug który nie jest bugiem

Weźmy pod lupę prostą pętle foreach w PHP, która zamienia we wszystkich elementach tablicy litery na duże:

$words = array('system', 'baza', 'telefon');
foreach ($words as &$item)
{
  $item = strtoupper($item);
}

Jak widać, kolejne elementy tablicy pobierane są po przez referencję, tj. tworzony jest wskaźnik na konkretny element a nie jego kopia w pamięci. Dlatego też, zmieniając w pętli zawartość zmiennej $item modyfikujemy pierwotną tablicę. W czym zatem tkwi problem?

Po wykonaniu powyższego kodu tablica $words wygląda następująco (var_dump($words)):

array(3) {
  [0]=>
  string(4) "SYSTEM"
  [1]=>
  string(4) "BAZA"
  [2]=>
  &string(6) "TELEFON"
}

Wszystko wygląda na pierwszy rzut oka tak jak oczekiwaliśmy. Spróbujmy zatem wyświetlić teraz wszystkie elementy:

foreach ($words as $item)
{
  echo $item.' ';
}

…w rezultacie otrzymujemy:

SYSTEM BAZA BAZA

Zdziwieni? Dzieje się tak dlatego, iż po pierwszej pętli zmienna $item, która jest wskaźnikiem, wciąż wskazuje na ost. element tablicy. W kolejnym przejściu, mimo iż nie użyliśmy już referencji, to zmienna $item wciąż jest typu wskaźnikowego i następuje wówczas wyciągnięcie aktualnego elementu w pętli foreach i przechowywanie go w ost. komórce tablicy którą analizujemy (zamiast w osobnej zmiennej). Z tej przyczyny, w końcowym kroku, ost. komórka zawiera wartość z kroku poprzedniego – stąd powtórzone słowo „BAZA”. Oryginalna tablica $words również posiada zmodyfikowaną wartość, co nie powinno mieć miejsca.

Aby uniknąć takich sytuacji możemy zastosować kilka rozwiązań. Pierwsze i chyba najlepsze, zwalniać zmienną iteracyjną po skończonej pętli jeśli używaliśmy referencji:

$words = array('system', 'baza', 'telefon');
foreach ($words as &$item)
{
  $item = strtoupper($item);
}
unset($item);

Drugie wyjście, użyć innej zmiennej (o innej nazwie) w ew. kolejnej pętli ;) . I na koniec, rozwiązanie na siłę:

$words = array('system', 'baza', 'telefon');
foreach ($words as $key => $item)
{
  $words[$key] = strtoupper($item);
}

…aczkolwiek jeśli używamy referencji to z reguły z powodu z którego nie możemy użyć w/w konstrukcji.

To zachowanie, mimo iż poprawne z punktu widzenia języka PHP, jest częstą przyczyną zgryzów u osób które nie zetknęły się nigdy z tym problemem. Często też jest to uważane za błąd samego parsera, który de facto nim nie jest.

  1. YanPL
    2 lutego 2010 at 18:41 | #1

    a to co:
    1 array(3) {
    2 [0]=>
    3 string(4) „MYSZ”
    4 [1]=>
    5 string(4) „OKNO”
    6 [2]=>
    7 &string(6) „SYSTEM”
    8 }
    bazowa tablica nie zawierała jakichś innych tekstów przypadkiem? :P

  2. 2 lutego 2010 at 20:09 | #2

    To po prostu funkcja strtoupper nieco pozmieniała ;)
    Oczywiście żartuję, dzięki… już poprawiłem.

  3. armando
    12 marca 2012 at 23:54 | #3

    ciekawy problem, dobrze że z var_dump-owałem tablicę bo bym tego nie zauważył

  1. Brak jeszcze trackbacków