この質問は foreach の内部的な挙動についてのものです。

PHP の foreach は、イテレーション対象の配列をコピーして動作しているのだと思っています。

ケース1:

$array = array(1, 2, 3);
foreach ($array as $item) {
    echo $item, ' ';
    $array[] = $item;
}
echo PHP_EOL, json_encode($array), PHP_EOL;

/* output:
    1 2 3
    [1,2,3,1,2,3]
 */

もし、イテレーション対象の配列そのものとして動作するなら、上記スクリプトは(ループ中に配列に対して要素を追加しているため)無限ループとなるはずだからです。

foreach についての PHP マニュアル を見たところ、以下の記述がありました。

foreach の実行開始時に内部配列ポインタは、 配列の先頭要素を指すように自動的にリセットされます。

そこでまず、以下のケース2を試してポインタの位置を確かめました。

ケース2:

$array = array(1, 2, 3);
// 配列のポインタを次に進めます。(が、foreach によりリセットされるため影響しないはずです)
each($array);

foreach ($array as $item) {
    echo $item, " ";
}

// foreach によりポインタが最後に進んでるはずなので、false が返って来るはず
var_dump(each($array));

/* output:
    1 2 3
    bool(false)
*/

予想通りの結果が得られました。

PHP マニュアル には以下の記述もあります。

foreach は内部の配列ポインタに依存するので、 ループ内で配列ポインタを変更すると予期せぬ振る舞いを引き起こします。

「予期せぬ振る舞い」が何なのか試すために、以下のケース3を実行してみます。

ケース3

$array = array(1, 2, 3);
foreach ($array as $key => $item) {
    echo $item, " ";
    reset($array); // 配列ポインタを強制リセット
}

/* output:
    1 2 3
*/

結果は、「予期せぬ振る舞い」ではなく、配列のコピーに対して foreach が動作しているように見えるだけでした。

質問

foreach はその配列のコピーで動作しているように見えますが、ループが終了した後に元配列の配列ポインタを末尾に移動しているようです。

foreach は本当のところどのように動作しているのでしょうか?

また、どのような場合に「予期せぬ振る舞い」が出るのでしょうか?

この質問は以下の本家StackOverflowの質問を翻訳・編集したものです。
How 'foreach' actually works @DaveRandom