読者です 読者をやめる 読者になる 読者になる

Perl日記

PerlとかRubyとかPHPとかPythonとか

foreachの要素の変数はループを抜けても保持されるのでunset()した

はまったのでメモ。

foreachで配列の中身を直接いじっているコードがあって、その後、同じ変数名を使っていたら中身が壊れてえらい目にあった。

先に結論を書くと、foreachのリファレンス変数はforeachを抜けても残っていて、変な動きをしていた。
そしてそれは、unset()で解除できる。

<?php

$users = [
    ['id' => 1, 'name' => 'Ichirou', 'age' => 39],
    ['id' => 2, 'name' => 'Jirou'  , 'age' => 36],
    ['id' => 3, 'name' => 'Saburou', 'age' => 33],
];

foreach ($users as &$user) {
    $user['age'] += 1;
}

// nanika

foreach ($users as $user) {
    echo sprintf("id: %d, name: %s, age: %d\n",
        $user['id'],
        $user['name'],
        $user['age']
    );
}

結果

id: 1, name: Ichirou, age: 40
id: 2, name: Jirou, age: 37
id: 2, name: Jirou, age: 37

id: 3は一体どこへ…。
ちなみに、// nanika の時点ではまだ、3つの要素は無事存在している。


foreachのドキュメントにちゃんと書いてあった。
PHP: foreach - Manual

警告
foreach ループを終えた後でも、 $value は配列の最後の要素を参照したままとなります。unset()でその参照を解除しておくようにしましょう。

なんでそんな面倒な仕様なんだ なるほど。


というわけで、unset()を挟んだらちゃんと意図したとおりに動いた。

<?php

foreach ($users as &$user) {
    $user['age'] += 1;
}
unset($user); // ★ここ

// nanika

foreach ($users as $user) {
    echo sprintf("id: %d, name: %s, age: %d\n",
        $user['id'],
        $user['name'],
        $user['age']
    );
}
id: 1, name: Ichirou, age: 40
id: 2, name: Jirou, age: 37
id: 3, name: Saburou, age: 34


これを突き止めるのに、時間がかかってしまった。
Perlのブロックレベルでのスコープが恋しい。