PHPのforeachで参照渡し(&)を使うと|二回目のループで、直前の値が勝手に上書きされていた
カテゴリの一覧に、同じ名前が二つ並んでいた。
DBの中身は正しいのに、画面だけがおかしい。
原因は、直前のforeachが参照渡しのまま変数を手放していなかったことだった。
カテゴリの一覧に、同じ名前が二つ並んでいた。
管理画面のカテゴリ設定で、本来は別々のはずの二つの行が、どちらも「キャンプ日誌」になっている。
データベースの中身を見直しても、値そのものは正しい。
おかしいのは表示のほうだった。
やっていたのは、名前を整えるためのforeachだった
カテゴリ名の前後の空白を削るだけの、ごく短い処理だった。
foreach ($categories as &$cat) { $cat['name'] = trim($cat['name']); }
配列そのものを書き換えたかったので、値ではなく参照で受け取っていた。
コピーを作って戻す手間を省ける、というだけの理由で選んだ書き方だった。
問題は、このforeachのすぐ下にあった
同じ関数の中に、別の配列を処理するforeachがもう一つあった。
foreach ($tags as $cat) { echo $cat['name']; }
変数名がたまたま同じ $cat だった。
タグの一覧を出しているだけの、参照とは無関係なループのつもりだった。
ところが出力を見ると、タグの最後の一つだけが、さっきのカテゴリの最後の要素と同じ内容にすり替わっていた。
ループを抜けても、$catは参照を持ったままだった
PHPには、if文やforeachの中だけで変数の寿命が終わる、という仕組みがない。
最初のforeachが終わった時点で、$cat はカテゴリ配列の最後の要素への参照を持ったまま、ただそこに残っていた。
二つ目のforeachで $cat に値を代入するたび、それは新しい変数に入っているのではなく、参照先であるカテゴリ配列の最後の要素を上書きしていることになる。
タグを一つ処理するごとに、カテゴリの最後の要素がタグの内容に置き換わっていく。
ループが終わったとき、カテゴリの最後の要素には、タグの最後の値が入っていた。
直したのは一行、unset($cat)だった
最初のforeachの直後に、これを足しただけだった。
foreach ($categories as &$cat) { $cat['name'] = trim($cat['name']); } unset($cat);
参照を明示的に切っておけば、次に同じ名前の変数を使っても、それはもう別物として扱われる。
直すのに数分もかからなかった。
見つけるのに、半日かかった。
見つかりにくかった理由
エラーは一切出ない。
構文としては完全に正しく、参照渡し自体もよくある書き方で、どちらのforeachも単体で見れば何もおかしくない。
怪しいのは常に変数名が偶然かぶった二つ目のループのほうに見えて、最初のforeachを疑うまでに時間がかかった。
再現条件も気まぐれで、カテゴリとタグの両方を同時に読み込むページでしか起きない。
いまは参照渡しのforeachを書いたら、閉じ括弧の次の行に unset() を書くところまでを一つの動作にしている。
&を使うと決めた時点で、後片付けも決めたことにする。
ほかにも書いています
note・スタンプ※noteは運営者が個人で書いているものです。
