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を使うようになりました。