最近学んだのでまとめます。
値渡しとは??
$a = 1;
$b = $a;
echo $b;
// 1
僕含め、よく皆さんが使っているであろうデフォルトの変数の代入です。
想定通りの挙動ですね。
内部的には、$a
の中にある1という値を複製して$b
に渡しています。
文字通り、「値渡し」ですね。
参照渡しとは??
$a = 1;
$b = &$a; // &を使うことで、$aを$bに参照渡しできる
$a = 2;
echo $b;
// 2
あれ?$b
には「1が入ってる$a
」を代入してるから、$b
をechoしたら1じゃないの??
しかし、2が出力されます。
これは、内部的には「変数$a
の場所を$b
に渡して」います。
$a
を$b
に参照渡しした結果、$a
の値を変えると$b
の値も変わります。
なので、$a
、$b
も2になってしまう仕組みです。
それを踏まえて下記コード。
<?php
$articles = [
['name' => 'アイス', 'price' => '100'],
['name' => 'ジュース', 'price' => '150'],
['name' => '唐揚げ', 'price' => '400'],
];
$object = new stdClass();
$array = [];
foreach ($articles as $article) {
$object->name = $article['name'];
$object->price = $article['price'];
$array[] = $object;
}
var_dump($array);
exit;
の結果が下記。
実行結果
array(3) {
[0]=>
object(stdClass)#1 (2) {
["name"]=>
string(9) "唐揚げ"
["price"]=>
string(3) "400"
}
[1]=>
object(stdClass)#1 (2) {
["name"]=>
string(9) "唐揚げ"
["price"]=>
string(3) "400"
}
[2]=>
object(stdClass)#1 (2) {
["name"]=>
string(9) "唐揚げ"
["price"]=>
string(3) "400"
}
}
期待値
array(3) {
[0]=>
object(stdClass)#2 (2) {
["name"]=>
string(9) "アイス"
["price"]=>
string(3) "100"
}
[1]=>
object(stdClass)#3 (2) {
["name"]=>
string(12) "ジュース"
["price"]=>
string(3) "150"
}
[2]=>
object(stdClass)#4 (2) {
["name"]=>
string(9) "唐揚げ"
["price"]=>
string(3) "400"
}
}
$object->name = $article['name'];
$object->price = $article['price'];
$array[] = $object;
(@shiracamus様より)
オブジェクトの場合は、オブジェクトへの参照を値(参照値)として変数に保持します。
オブジェクトへの参照値を渡すと「オブジェクト共有」することになります。
で、$object
の中身を作ったタイミングで $array[]
に代入しています。
しかし、オブジェクトへの参照値を渡しているため、次のループで、$array[]
の中の$object
を書き換えてしまいます。
つまり、$array[]
の中の$object
と、$object
は一心同体状態になります。
$object
に変更があったら、$array[]
内の$object
にも同じように変更が加わります。
おそらく内部的な挙動はこうです。
1回目のループ
$array = [
(object)['name' => 'アイス', 'price' => '100'],
];
2回目のループ
$array = [
(object)['name' => 'ジュース', 'price' => '150'], --> 本来はアイス、100が入るはず
(object)['name' => 'ジュース', 'price' => '150'],
];
3回目のループ
$array = [
(object)['name' => '唐揚げ', 'price' => '400'], --> 本来はアイス、100が入るはず
(object)['name' => '唐揚げ', 'price' => '400'], --> 本来はジュース、150が入るはず
(object)['name' => '唐揚げ', 'price' => '400'],
];
回避策
参照値をそのまま渡すのではなく、参照先のオブジェクトの中身をコピーして渡してあげます。
<?php
$articles = [
['name' => 'アイス', 'price' => '100'],
['name' => 'ジュース', 'price' => '150'],
['name' => '唐揚げ', 'price' => '400'],
];
$object = new stdClass();
$array = [];
foreach ($articles as $article) {
$object->name = $article['name'];
$object->price = $article['price'];
$array[] = clone $object;
}
var_dump($array);
exit;
cloneを使うことによって、$object
の値だけをコピーして $array[]
の中に代入することができます(値渡し)
結果、期待値通りの挙動となります。