1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPの落とし穴 1: foreachと参照の組み合わせ

Last updated at Posted at 2019-10-17

次の 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 に続くかは未定です:heart:

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?