Help us understand the problem. What is going on with this article?

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

次の 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:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした