PHP5までのforeachでは、配列の内部ポインタに関連して説明しづらい未定義の挙動がありました。これがPHP7で整理されました。
値渡しのforeachループで配列の内部ポインタを使わなくなった
PHPの配列は、「内部ポインタ」を持った構造になっています。これは「この配列をどこまで読んだか」を管理するもので、current(), next(), reset()といった滅多に使わない関数を実現するのに使われています。
ところで、PHP5まではforeachでも内部ポインタが利用されていました。
<?php
$a = [1,2,3];
foreach ($a as $v) {
echo $v . " - " . current($a) . "\n";
}
/*
PHP5での出力
1 - 2
2 - 2
3 - 2
PHP7での出力
1 - 1
2 - 1
3 - 1
*/
この例ではforeachが配列の内部ポインタを使っているため、初回のcurrent()のタイミングで配列のコピーオンライトが起こり、上のような出力になります。
PHP7では同じ状況で配列の内部ポインタとは別のポインタが使われるようになりました。そのため、foreachループの内側でcurrent()関数などを使った場合の挙動がPHP5までとは変わっています。
また、この変更により配列のコピーオンライトが減り、性能面でもプラスに働くようです。
参照渡しのforeachループでは配列の内部ポインタを使う(PHP5と同じ)
foreachループで配列を参照渡しした場合は基本的にPHP5と同じで、内部ポインタが使われます。大半の挙動はPHP5と変わっていませんが、一部のエッジケースでは挙動が変わることもあるようです。個人的にforeachの参照渡しは怖くて使わない派なので詳細は追いかけていませんが、興味がある方はRFCを確認してみてください。
また、TraversableなオブジェクトについてはPHP5までと何も変わっていません。Traversable以外のオブジェクトのforeachは配列の参照渡しと同様の挙動です。
foreach関連opcodeの新設
PHP5ではforeachに対応するopcodeはFE_RESET FE_FETCHでした。これが、PHP7では値渡しのforeach用のFE_RESET_R FE_FETCH_Rおよび参照渡しのforeach用のFE_RESET_RW FE_FETCH_RWに分かれました。
また、PHP5では配列に対応するテンポラリ変数の解放にFREE opcodeが使われていましたが、PHP7では新設されたFE_FREE opcodeを使うようになりました。