次の PHP のコードを見てください
<?php
function f1($array)
{
foreach ($array as &$item) {
// noop
}
foreach ($array as $item) {
// noop
}
return $array;
}
$data = [1, 2, 3, ];
$result = f1($data);
echo 'result = ' . json_encode($result) . PHP_EOL;
f1
は渡された配列 $array
を
特になにもしない 2 回のループをくぐらせて
そのまま与えられた配列 $array
を返す関数です。
このコードの実行結果はどうなるでしょうか?
結果
この結果は以下になります:
result = [1,2,2]
何もしてないのでこれは [1,2,3]
だろうって予想してしまったのですが
これは予想に反して [1,2,2]
となります。
確認
動作を確認するために
逐一、値を var_dump
するようにしてみます:
function f2($array)
{
echo '# first loop begins.' . PHP_EOL;
foreach ($array as &$item) {
var_dump($array);
}
echo '# second loop begins.' . PHP_EOL;
foreach ($array as $item) {
var_dump($array);
}
echo '# loops end.' . PHP_EOL;
return $array;
}
この結果は以下です:
# first loop begins.
array(3) {
[0]=>
&int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(1)
[1]=>
&int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
&int(3)
}
# second loop begins.
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
&int(1)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
&int(2)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
&int(2)
}
# loops end.
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(2)
}
一回目のループでは配列内の値は特に変化してませんが (参照されてるくらいですね)
二回目のループでは配列の最後の値が変化しはじめています。
ここから二回目のループが配列の値を変更していることがわかります。
更に確認
function f3($array)
{
foreach ($array as &$item) {
// noop
}
var_dump($array, $item);
foreach ($array as $item) {
// noop
}
return $array;
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
&int(3)
}
int(3)
一回目のループと二回目のループ間に変数 $item
が生存していて
まだ参照が残っていることに注目してください。
さらに
二回目のループで foreach ($array as $item)
をするときに
$item
に $array
の要素が代入されていってるようすなのがわかりますね。
すなわち
function f4($array)
{
$item = &$array[0];
$item = &$array[1];
$item = &$array[2];
$item = $array[0];
$item = $array[1];
$item = $array[2];
return $array;
}
のような動作をしています。
1回目のループで使用した参照が生きていて
2回目のループで参照経由で値が代入されるのが原因ということがわかります。
つまり
PHP においては変数がブロックスコープを持ったりしないことに注意して
function f1($array)
{
foreach ($array as &$item) {
// noop
}
unset($item);
foreach ($array as $item) {
// noop
}
return $array;
}
参照を持つループの一時変数を unset
するか
function f1($array)
{
foreach ($array as &$itemRef) {
// noop
}
foreach ($array as $item) {
// noop
}
return $array;
}
ループごとに変数名を変更して自明かつ変更があっても
困らないようにする必要があるわけでした。
単純なふうに見えて注意しないと予想外の動作をするコードでした。
落とし穴 2 に続くかは未定です