PHPでforeachの参照渡しをする時はunsetをセットで書く(もしくは使わない)
はじめに
以前レビュー中にforeach
の参照渡しが書かれているコードを読んでいて
動かしてみたら想定外の挙動をしたので備忘録として残します。
クイズ
$prime_number
の値は最終的には何になるでしょうか?
<?php
$prime_number = array(2,3,5,7);
foreach ($prime_number as &$value) {
$value = $value * 2;
}
foreach ($prime_number as $key => $value) {
echo "キー:" .$key ."\n";
echo "値:" .$value ."\n";
}
print_r($prime_number);
予想
最初のforeach
で参照渡しをしているので
$prime_number = array(4,6,10,14);
になる。
2つ目のforeach
はecho
しているだけなので$prime_number
の値は変わらない。
echoは当然
キー:0
値:4
キー:1
値:6
キー:2
値:10
キー:3
値:14
を返すだけのシンプルなロジック
答え
キー:0
値:4
キー:1
値:6
キー:2
値:10
キー:3
値:10 //あれ・・・
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 10 //こんなはずでは・・・
)
考察
公式リファレンスにももちろん書いているのですが
foreach
で参照渡しを使った時
1つ目のforeach
の最後のループが終わった後
$value
は$prime_number[3]
を指したままになります。
ですので2つ目のforeach
の処理1周毎に$prime_number[3]
の値は4,6,10,10と上書きされます。
分かりやすいように各所で値を出してみます。
<?php
$prime_number = array(2,3,5,7);
foreach ($prime_number as &$value) {
$value = $value * 2;
}
echo '1回目のforeachの後の$prime_number'."\n";
print_r($prime_number);
echo '↓から2回目のforeach'."\n";
foreach ($prime_number as $key => $value) {
echo "キー:" .$key ."\n";
echo "値:" .$value ."\n";
print_r($prime_number);
}
echo '最終的な$prime_number'."\n";
print_r($prime_number);
結果
1回目のforeachの後の$prime_number
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 14
)
↓から2回目のforeach
キー:0
値:4
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 4
)
キー:1
値:6
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 6
)
キー:2
値:10
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 10
)
キー:3
値:10
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 10
)
最終的な$prime_number
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 10
)
修正方法
期待する結果を得るためには
1回目のforeach
の後にunset
をすればいいので
<?php
$prime_number = array(2,3,5,7);
foreach ($prime_number as &$value) {
$value = $value * 2;
}
unset($value); //ここだけ追加した
echo '1回目のforeachの後の$prime_number'."\n";
print_r($prime_number);
echo '↓から2回目のforeach'."\n";
foreach ($prime_number as $key => $value) {
echo "キー:" .$key ."\n";
echo "値:" .$value ."\n";
print_r($prime_number);
}
echo '最終的な$prime_number'."\n";
print_r($prime_number);
結果
1回目のforeachの後の$prime_number
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 14
)
↓から2回目のforeach
キー:0
値:4
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 14
)
キー:1
値:6
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 14
)
キー:2
値:10
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 14
)
キー:3
値:14
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 14
)
最終的な$prime_number
Array
(
[0] => 4
[1] => 6
[2] => 10
[3] => 14
)
無事に$prime_number = array(4,6,10,14);
になりました。
ちなみに参照渡しがトリッキーだと思うので、参照渡しを使わずに書くとこんな感じです。
<?php
$prime_number = array(2,3,5,7);
foreach ($prime_number as $key => $value) {
$value = $value * 2;
$prime_number[$key] = $value;
}
echo '1回目のforeachの後の$prime_number'."\n";
print_r($prime_number);
echo '↓から2回目のforeach'."\n";
foreach ($prime_number as $key => $value) {
echo "キー:" .$key ."\n";
echo "値:" .$value ."\n";
print_r($prime_number);
}
echo '最終的な$prime_number'."\n";
print_r($prime_number);
速度検証
参照渡しを使う場合と使わない場合
同じロジックにしたらどちらが早いのか手元で実験してみました。
100万個の配列を作って各要素を2倍する時間を10回ずつ測定
参照渡し有り
<?php
$array=array();
for ($i = 1; $i <= 1000000; $i++) {
$array[$i] = $i;
}
$time_start = microtime(true);
foreach ($array as &$value) {
$value = $value * 2;
}
$time = microtime(true) - $time_start;
$time = round($time, 4);
echo $time ."秒";
10回実行した平均:0.02236秒
参照渡し無し
<?php
$array=array();
for ($i = 1; $i <= 1000000; $i++) {
$array[$i] = $i;
}
$time_start = microtime(true);
foreach ($array as $key => $value) {
$value = $value * 2;
$array[$key] = $value;
}
$time = microtime(true) - $time_start;
$time = round($time, 4);
echo $time ."秒";
10回実行した平均:0.02027秒
まとめ
若干ではあるが参照渡しを使わない方が早かったです(参照渡し[有り]0.02236秒 [無し]0.02027秒)
参照渡しを使う方がキーの値をforeach
で指定せずとも配列の値を書き換えられるが
unset
を忘れると不具合の原因になりかねないので複数人で読むようなプロダクトには向いていないかもしれないです。
ソースの量を減らすことも大切ですが、誰にでも読みやすいコードだ可読性が上がっていいかもしれませんね。
NE株式会社のエンジニアを中心に更新していくPublicationです。 NEでは、「コマースに熱狂を。」をパーパスに掲げ、ECやその周辺領域の事業に取り組んでいます。 Homepage: ne-inc.jp/
Discussion
個人的に参照渡しや値渡しにあまり馴染みがなかったので勉強になりました!
参照渡し無しのやり方として、別の変数に格納するやり方も自分は良く使っている気がします
コメントありがとうございます!!!
確かに、別の変数に格納する方が一般的な気がします。
こちらこそ勉強になりました!