The Daily WTF
Okazywać się zaczęło, że po wysłaniu do tworzonej aplikacji tekstu plus tekstu dotyczącego podkategorii w niewytłumaczalny sposób tekst dotyczący podkategorii drugiej lub późniejszej w kolejności ginął, a zastępowany był przez multiplikowany tekst dotyczący pierwszej podkategorii. Niezrozumiałe? Zaraz wyjaśnię.
I jeszcze do tego na moim kodzie własnym takie WTF dostałem... choć sam kod też się chyba do wysiwtf.pl nadawał ;-)
Istniała tablica asocjacyjna podkategorii danej kategorii w której znajduje się obiekt, zawierająca coś takiego:
Array
(
[0] => Array
(
[IDElem] => 3
[Description] => Malowanie
)
[1] => Array
(
[IDElem] => 5
[Description] => Coś jeszcze
)
)
I istniały pola formularza, które tą wartość IDElem zawierały, by można było rozróżnić na temat jakiej podkategorii wysyłamy dane. Żeby poznać numerki tych pól formularza wystarczy przejść po wszystkich elementach tej tablicy, nazwanej u mnie $subs i podostawiać z przodu pewien ciąg (u mnie "content") by poznać, ze wysłane z formularza dane były w polach "content3" i "content5". Jasne mam nadzieję i zrozumiałe ;-) Metoda jest strasznie niby zagmatwana, ale w rzeczywistości bardzo ładnie się miała sprawować. Tak więc, skoro mamy przejść po wszystkich polach tej tablicy $subs, to najbardziej logiczne wydaje się użycie, bardzo przeze mnie lubianej, konstrukcji foreach. Tak też zrobiłem.
foreach ($subs as $sub)
{
$rules['content' . $sub['IDElem']] = 'numeric';
}
Gdzie $rules to tablica używana w procesie walidacji danych. Nic nadzwyczajnego. Teraz pojawia się jednak zagadka. Przed tą pętlą $subs wygląda tak, jak pokazałem wyżej. A po niej? Skrajnie inaczej.
Array
(
[0] => Array
(
[IDElem] => 3
[Description] => Malowanie
)
[1] => Array
(
[IDElem] => 3
[Description] => Malowanie
)
)
"WTF?!" pomyślałem i w mojej głowie nie ma w tym momencie żadnego pomysłu dlaczego tak miało by się dziać. Potem siadłem do prostego skryptu który miał zweryfikować że jest to błąd PHP, a nie mój błąd.
<?php
$subs = array(array('IDElem' => 3, 'Description' => 'Malowanie'), array('IDElem' => 5, 'Description' => 'Cokolwiek'));
print_r($subs);
$foo = array();
foreach ($subs as $sub)
$foo['test' . $sub['IDElem']] = 'bar';
print_r($foo);
print_r($subs);
?>
Pokazuje dane (zaskoczeni?) prawidłowo (see example). Zatem błąd musi leżeć jeszcze gdzieś indziej. Przyjrzałem się mojemu oryginalnemu fragmentowi kodu - były tam wcześniej jeszcze pewne linijki które przez foreach się do $subs odwoływały - i modyfikowały tablicę. Stworzyłem zatem coś przykładowego:
foreach ($subs as &$sub)
{
$sub['baz'] = 'foo' . $sub['IDElem'];
}
Co zostało wstawione w odpowiednie miejsce. Kolejna próba sprawdzenia co jest nie tak... i sukces (live example). Jeżeli można to tak nazwać. Tak więc, czy jest to błąd padło kolejne pytanie - mój PHP w (niezwykle aktualnej) wersji 5.1.4 tak sądził, więc może jest to poprawione w wersji nowszej? Sięgnąłem do PHP 5.2.3 na serwerze... i niestety. Sytuacja analogiczna. Ściągnąłem zatem PHP 5.2.4 prosto z pl.php.net. I magiczne zaklęcie w trybie tekstowym ujawniło... to samo.
Jakieś dwie godziny spędziłem nad tym (wliczając w to naprawienie tego w kodzie produkcyjnym poprzez zastąpienie foreach przez for odpowiedni) i jestem coraz bardziej przekonany, że to jest błąd PHP, a nie mój sposób myślenia.
Jeżeli ktoś jeszcze potwierdzi (przypadki testowe jeden i dwa tutaj), to ja się ucieszę (no, powiedzmy) i rzucę to na bugs.php.net. Może.
01 września 2007 14:27:47
Bez czytania wpisu: jeśli komuś coś w PHP nie wychodzi, to ten język ma taką zszarganą opinię, że normalną reakcją jest okrzyk: „znalazłem błąd w PHP”! :P
Po czytaniu: http://pl.php.net/manual/pl/language.references.whatdo.php – patrz komentarze.
01 września 2007 14:44:10
Mam ochotę poprzeklinać szczerze. W komentarzach jest problem, prawie taki sam. I możliwe, że o to samo chodzi.
foreach ($subs as $sub) $foo['test' . $sub['IDElem']] = 'bar';Zmiana tego kodu w drugim testcase na foreach z referencją likwiduje problem. Pozostaje jednak jak dla mnie pytanie – jeżeli to nie jest błąd, a przewidziane zachowanie, to dlaczego u licha?
Zdrowy rozsądek działa w ten sposób, że za pierwszym razem operujemy na oryginalnej tablicy, za drugim razem kopiujemy zmienioną tablicę i z niej wybieramy wartości. Dlaczego zatem dostajemy coś kompletnie niezrozumiałego?
„Porzućcie wszelką logikę, wy którzy tu programujecie”?
01 września 2007 17:01:44
Modyfikacje kolekcji foreach’a w samym foreach’u to nie najlepszy pomysł (np. C# wogóle tego zabrania). Rozwiązanie przez referencję jest lepsze, ale najlepiej wogóle tego unikać (bo jest to kod jak widać podatna na trudne do wykrycia i zrozumienia błędy)
28 września 2007 02:44:02
Po pierwszym foreach $sub nadal jest referencją do drugiego elementu, więc drugi foreach powoduje nadpisanie. Zmień nazwę znmiennej w drugiej pętli i będzie OK.