はじめに
この記事を理解するには、変数、関数、クラスの基本的な知識が必要です。
PHP内部で参照の代入がどのように処理されているのか見ていくことで理解を深めることが目的です。
PHPマニュアルでは参照ではなくリファレンスという表記をしておりますが、リファレンスより参照と呼ぶ方が多いと思われるため、この記事ではリファレンスではなく参照と記載します。
私はPHPの勉強中で他言語の知識はありません。
おそらくですが、他言語の参照とPHPの参照は異なる部分があるのかと思います。
この記事の参照という言葉は、PHPマニュアルのリファレンスのことを指しています。
この記事は下記の記事の続きになります。
上記の記事で説明している内容は改めて説明しませんので、最初に上記の記事をご覧ください。
今回、下記の流れでまとめました。
参照の記述
スカラー型の参照代入
サンプルケース1
サンプルケース2
サンプルケース3
配列型の参照代入
サンプルケース1
サンプルケース2
サンプルケース3
foreachと参照
サンプルケース1
サンプルケース2
オブジェクト型の参照代入
サンプルケース1
私自身未熟ですので説明に誤りがあるかもしれません。
誤りがあれば、ご指摘ください ((_ _ (´ω` )ペコ
参照の記述
変数名の前に &
を記述すると参照になります。
参照による代入を行うには $a = &$b
または $a =& $b
のように記述します。
&
を =
の前に記述して $a &= $b
とした場合は、複合演算子のビット演算の代入になり、参照とは全くの別物です。
$a &= $b
という記述は $a = $a & $b
と同義ですので注意してください。
前回の記事の値の代入と同様に、スカラー型、配列型、オブジェクト型の参照代入について見ていきましょう。
スカラー型の参照代入
スカラー型(整数型)を参照代入すると、PHP内部ではどのように処理されるのか見ていきましょう。
サンプルケース1(スカラー型の参照代入)
$a = 1;
$b = &$a;
$b = 2;
echo $a // 結果:2
echo $b // 結果:2
2行目で &$a
と記述していますので $a
の参照を $b
に代入しています。
参照代入すると $a
と $b
は運命共同体になり、$a
と $b
は全く同じ値を指し示します。
$a
の値を変更すると、同じ値を指し示している $b
にも影響します。
逆に $b
の値を変更すると、同じ値を指し示している $a
にも影響します。
PHP内部ではどのようになっているのか1行ずつ見ていきましょう。
1行目
$a = 1;
$a
は (int)1
を指し示します。
2行目
$b = &$a;
&$a
と記述することで、参照代入しています。
$a
と $b
は運命共同体になるため、全く同じ値を指し示します。
refcount
は 2
になります。
参照のため is_ref
は 1
になります。
3行目
$b = 2;
$b
に 2
を代入しました。
$b
と $a
が指し示している値は (int)2
になります。
最後に echo
で $a
と $b
を表示すると両方とも 2
と表示されます。
1行ずつPHP内部でどのように処理されているのか見ていくことで理解できたかと思います。
もう一つ別のケースを見てみましょう。
サンプルケース2(スカラー型の参照代入)
$a = 1;
$b = &$a;
$c = &$b;
$c = 2;
$a = 3;
2行目で $b
に $a
を参照代入しています。
3行目で $c
に $b
を参照代入しています。
先ほども説明したとおり、参照代入すると運命共同体になり、全く同じ値を指し示すようになります。
$a
と $b
と $c
は全く同じ値を指し示していると言えます。
PHP内部ではどのようになっているのか1行ずつ見ていきましょう。
1行目
$a = 1;
$a
は (int)1
を指し示します。
2行目
$b = &$a;
&$a
と記述することで、参照代入しています。
$a
と $b
は運命共同体になるため、全く同じ値を指し示します。
refcount
は 2
になります。
参照のため is_ref
は 1
になります。
3行目
$c = &$b;
&$b
と記述することで、参照代入しています。
$b
と $c
は運命共同体になるため、全く同じ値を指し示します。
$a
と $b
はもともと同じ値を指し示していたので、$a
と $b
と $c
は全く同じ値を指し示します。
(int)1
の refcount
は 3
になります。
4行目
$c = 2;
$c
に 2
を代入しています。
$a
と $b
と $c
が指し示している値は (int)2
になります。
5行目
$a = 3;
$a
に 3
を代入しています。
先ほどと同じく、$a
と $b
と $c
が指し示している値は (int)3
になります。
1行ずつPHP内部でどのように処理されているのか見ていくことで理解できたかと思います。
もう一つ別のケースを見てみましょう。
サンプルケース3(スカラー型の参照代入)
$a = 1;
$b = $a;
$c = 'Hello';
$d = $c;
$e = &$d;
4行目まで普通に値渡しをしています。
5行目で $e
に $d
を参照代入しています。
PHP内部ではどのようになっているのか1行ずつ見ていきましょう。
1行目
$a = 1;
$a
は (int)1
を指し示します。
2行目
$b = $a;
参照代入ではなく、値の代入のため $a
の値が複製されて $b
に代入されますが、コピーオンライトのため現時点ではまだ値の複製は行なわれず、同じ値を指し示します。
どちらかの値に変更があった時点で値の複製と書き換えが行われます。
3行目
$c = 'Hello';
$c
は (string)'Hello'
を指し示します。
4行目
$d = $c;
参照代入ではなく、値の代入のため $c
の値が複製されて $d
に代入されますが、コピーオンライトのため現時点ではまだ値の複製は行なわれず、同じ値を指し示します。
どちらかの値に変更があった時点で値の複製と書き換えが行われます。
5行目
$e = &$d;
$e
に $d
を参照代入しています。
$e
と $d
は運命共同体になり、全く同じ値を指し示します。
先ほどまで $d
は $c
と同じ値を指し示していましたが、参照の代入をしたため $d
と $e
は全く同じ値を指し示す必要があります。
つまり、この時点で値の複製が必要になったということです。
値の複製が行われ、$d
と $e
は全く同じ値を指し示し、その値の refcount
は 2
になり、is_ref
は 1
になります。
それにあわせて $c
が指し示している値の refcount
は 1
になります。
今まで値自体に変更があったときに値の複製と書き換えが行われる例だけを見てきましたが、値自体に変更がなくても、参照によって値の複製が必要なときは、それにあわせて値の複製が行われます。
次は配列型の参照代入について見ていきましょう。
配列型の参照代入
配列型を参照代入すると、PHP内部ではどのように処理されるのか見ていきましょう。
サンプルケース1(配列型の参照代入)
$a = 'a';
$b = 'b';
$c = 'c';
$arr = [$a, $b, &$c];
$a = 'd';
$b = 'e';
$c = 'f';
このコードは下記の回答からの引用です。
引用元:phpではarrayは参照型ではないのでしょうか?ある関数にarrayの変数を渡し... - Yahoo!知恵袋
最後に var_dump($arr)
で表示すると下記のようになります。
array(3) {
[0] => string(1) "a"
[1] => string(1) "b"
[2] => &string(1) "f"
}
なぜ、このような結果になるのか1行ずつ見ていきましょう。
1〜3行目
$a = 'a';
$b = 'b';
$c = 'c';
変数名は各値を指し示します。
4行目
$arr = [$a, $b, &$c];
$arr
に配列を代入しています。
配列の各値は $a
と $b
と $c
ですが、$c
だけ参照代入です。
参照代入のため $c
と $arr[2]
は運命共同体になり、全く同じ値を指し示します。
(string)'c'
の refcount
は 2
になり、is_ref
は 1
になります。
他の値は現時点で複製する必要がないので、$a
と $b
と同じ値を指し示します。
どちらかの値に変更があった時点で値の複製と書き換えが行われます。
5〜7行目
$a = 'd';
$b = 'e';
$c = 'f';
$a
と $b
と $c
に別の値を代入しています。
$c
と $arr[2]
は参照で同じ値を指し示しています。
そのため、最終的に $arr
の配列の値を出力すると下記のようになります。
array(3) {
[0] => string(1) "a"
[1] => string(1) "b"
[2] => &string(1) "f"
}
もう一つ別のケースを見てみましょう。
サンプルケース2(配列型の参照代入)
$a = [1,2,3];
$b = &$a;
$a[0] = 'a';
$b[2] = 'c';
最後に var_dump($a)
で表示すると下記のようになります。
array(3) {
[0] => string(1) "a"
[1] => int(2)
[2] => string(1) "c"
}
なぜ、このような結果になるのか1行ずつ見ていきましょう。
1行目
$a = [1,2,3];
変数名 $a
は (array)
を指し示し、配列の各キーは各値を指し示します。
2行目
$b = &$a;
$b
に $a
を参照代入しています。
$b
と $a
は運命共同体になり、全く同じ (array)
を指し示します。
(array)
の refcount
は 2
になり、is_ref
は 1
になります。
3行目
$a[0] = 'a';
$a[0]
に a
を代入しています。
$a
の配列キー 0
の値は (string)'a'
になります。
4行目
$b[2] = 'c';
$b
の配列キー 2
の値は (string)'c'
になります。
$a
と $b
は全く同じ (array)
を指し示しているため、最後に var_dump($a)
で表示すると下記のようになります。
array(3) {
[0] => string(1) "a"
[1] => int(2)
[2] => string(1) "c"
}
もう一つ別のケースを見てみましょう。
サンプルケース3(配列型の参照代入)
$a = [1,2,3];
$b = &$a[1];
$c = $a;
$c[0] = 'a';
$c[1] = 'b';
$c[2] = 'c';
このコードは下記の記事からの引用です。
最後に var_dump($a)
で表示すると下記のようになります。
array(3) {
[0] => int(1)
[1] => &string(1) "b"
[2] => int(3)
}
なぜ、このような結果になるのか1行ずつ見ていきましょう。
1行目
$a = [1,2,3];
変数名 $a
は (array)
を指し示し、配列の各キーは各値を指し示します。
2行目
$b = &$a[1];
$b
に $a
の配列キー 1
の値を参照代入しています。
$b
と $a[1]
は運命共同体になり、全く同じ値を指し示します。
(int)2
の refcount
は 2
になり、is_ref
は 1
になります。
3行目
$c = $a;
$c
に $a
の配列を代入しています。
配列は複製されて代入されますが、コピーオンライトのため、現時点では配列は複製されず、$c
は $a
と同じ (array)
を指し示します。
変数に別の値を代入したり、配列の値を変更するなど、配列の複製が必要になった時点で、複製されます。
4行目
$c[0] = 'a';
$c
の配列キー 0
に a
を代入しました。
配列の複製が必要になったため、この時点で配列の複製が行なわれます。
$a
と $c
は別々の配列を指し示します。
$a
が指し示している (array)
の refcount
は 1
になります。
配列を複製する前から (int)2
の is_ref
は 1
でした。
この値は参照されています。
もともと (int)2
は参照されており、配列が複製されても、参照なのは変わりません。
$a[1]
と $b
と $c[1]
は全く同じ (int)2
を指し示します。
それにあわせて refcount
が 3
になります。
この行では $a[2]
と $c[2]
の値は同じです。
$c[2]
と $a[2]
は同じ値ですので、この時点では複製されず、同じ (int)3
を指し示します。
(int)3
の refcount
は 2
になります。
コピーオンライトのため、同じ値を指し示していますが、どちらかの値に変更があった時点で複製されて書き換えが行われます。
5・6行目
$c[1] = 'b';
$c[2] = 'c';
$c[1]
に b
を代入しています。
$c[1]
と $a[1]
と $b
が指し示している値は (string)'b'
になります。
$c[2]
は (string)'c'
を指し示すようになります。
それにあわせて (int)3
の refcount
が 1
になります。
最後に var_dump($a)
で表示すると下記のようになります。
array(3) {
[0] => int(1)
[1] => &string(1) "b"
[2] => int(3)
}
1行ずつ見てきたので、なぜこのような結果になるのか理解できたかと思います。
次は foreach
と参照について見ていきましょう。
foreachと参照
既に foreach
については理解しているかもしれませんが、簡単に foreach
の使い方について説明します。
foreach
は配列または連想配列のデータを取り出すのに便利な制御構文です。
値のみ取り出したいときは、下記のように記述します。
foreach(取り出したい配列または連想配列 as 値の仮変数) {
// ループ内で実行する処理
}
配列にある名前を取り出して表示するなら、下記のように記述します。
$data = ['佐藤', '鈴木', '高橋'];
foreach($data as $value) {
echo "名前は {$value} です。";
}
配列のキーも取り出したいときは、下記のように記述します。
foreach(取り出したい配列または連想配列 as キーの仮変数 => 値の仮変数) {
// ループ内で実行する処理
}
連想配列にある名前と性別を取り出して表示するなら、下記のように記述します。
$data = ['佐藤' => '男', '鈴木' => '女','高橋' => '男'];
foreach($data as $key => $value) {
echo "{$key} さんの性別は {$value} です。";
}
foreach
の使い方については理解できたかと思います。
基の配列の値を2倍にするような foreach
を記述してみましょう。
$arr = [1,2,3];
foreach ($arr as $a) {
$a = $a * 2;
}
var_dump($arr);
最後に var_dump
で $arr
を出力すると下記のようになります。
array(3) {
[0] => int(1)
[1] => int(2)
[2] => int(3)
}
foreach
内で2倍にしても基の配列は変わっていません。
なぜかというと foreach
の仮変数 $a
に値を代入しているためです。
foreach
は配列の値の数だけ繰り返し処理をしますが、最初の処理では $a = $arr[0]
として仮変数 $a
に代入していると言えます。
前回の記事でも説明していますが、値を代入すると値は複製されて別々の値になります。
そのため、基の $arr
には変化がありませんでした。
基の $arr
の配列を値を foreach
内で変えるには参照を使う方法があります。
$arr = [1,2,3];
foreach ($arr as &$a) {
$a = $a * 2;
}
var_dump($arr);
最後に var_dump
で $arr
を出力すると下記のようになります。
array(3) {
[0] => int(2)
[1] => int(4)
[2] => &int(6)
}
(参照を使うのは推奨されないやり方のようですが)期待通りの結果になりました。
基の配列の値が変わっているのがわかります。
ただ、foreach
の仮変数を参照にするときに気をつけるべき点と言いますか、落とし穴があります。
どのような落とし穴があるのか foreach
と参照を使った例を見ていきましょう。
サンプルケース1(foreachと参照)
$arr = [1,2,3];
foreach ($arr as &$a) {
$a = $a * 2;
}
$a = 10;
var_dump($arr);
このコードは下記の記事からの引用です。
引用元:Qiita - 初心者を戒めるPHP リファレンス(参照)はやめろ by @tadsan さん
最後に var_dump($arr)
で表示すると下記のようになります。
array(3) {
[0] => int(2)
[1] => int(4)
[2] => &int(10)
}
foreach
に慣れていない方には foreach
がどのような処理をしているのかわからないかと思います。
今回の foreach
は3回ループ処理をしており、仮変数で指定した $a
を使ってループ処理をしています。
foreach
を使わずに記述すると下記のようになります。
$arr = [1,2,3];
// foreach の1週目
$a = &$arr[0];
$a = $a * 2;
// foreach の2週目
$a = &$arr[1];
$a = $a * 2;
// foreach の3週目
$a = &$arr[2];
$a = $a * 2;
$a = 10;
var_dump($arr);
この記述を見るとわかりますが、foreach
の仮変数 $a
はグローバルスコープにあります。
$a
がグローバルスコープにあるということは foreach
が終わっても、$a
は foreach
が終了した時点の状態を保っているということです。
foreach
の仮変数 $a
はグローバルスコープにあり、foreach
が終了しても最後の状態を保っているというのがポイントになりますので、覚えておいてください。
それでは、1行ずつ見ていきましょう。
1行目
$arr = [1,2,3];
変数名 $arr
は (array)
を指し示し、配列の各キーは各値を指し示します。
2行目(foreachの1週目)
$a = &$arr[0];
ここからは foreach
内の1週目の処理です。
foreach
の仮変数で記述した $a
に $arr[0]
を参照代入しています。
$a
と $arr[0]
は運命共同体になり、全く同じ値を指し示します。
(int)1
の refcount
は 2
になり、is_ref
は 1
になります。
3行目(foreachの1週目)
$a = $a * 2;
$a
の値に $a * 2
の値を代入しています。
$a
は 1
ですので、1 * 2
の値が代入されます。
$a
と $arr[0]
が指し示す値は (int)2
になります。
4行目(foreachの2週目)
$a = &$arr[1];
ここからは、foreach
内の2週目の処理です。
$a
と $arr[1]
は運命共同体になり、全く同じ値を指し示します。
(int)2
の refcount
は 2
になり、is_ref
は 1
になります。
もともと指し示していた (int)2
の refcount
は 1
になり、is_ref
は 0
になります。
$a
に対して $a = &$arr[0]
のあとに $a = &$arr[1]
と記述しました。
同じ変数に2回参照代入すると、最後の参照代入のみ有効です。
そのことは上図からもわかりますが、2回参照代入しても $arr[0]
と $arr[1]
と $a
の3つが全く同じ値を指し示すということはありません。
5行目(foreachの2週目)
$a = $a * 2;
$a
の値に $a * 2
の値を代入しています。
$a
は 2
ですので、2 * 2
の値が代入されます。
$a
と $arr[1]
が指し示す値は (int)4
になります。
6行目(foreachの3週目)
$a = &$arr[2];
ここからは、foreach
内の3週目の処理です。
$a
と $arr[2]
は運命共同体になり、全く同じ値を指し示します。
(int)3
の refcount
は 2
になり、is_ref
は 1
になります。
もともと指し示していた (int)4
の refcount
は 1
になり、is_ref
は 0
になります。
7行目(foreachの3週目)
$a = $a * 2;
$a
の値に $a * 2
の値を代入しています。
$a
は 3
ですので、3 * 2
の値が代入されます。
$a
と $arr[2]
が指し示す値は (int)6
になります。
これで foreach
の処理は終了です。
8行目
$a = 10;
冒頭でも説明しましたが、foreach
の仮変数 $a
はグローバルスコープにあります。
foreach
が終了しても $a
は最後の状態を保っています。
$a
と $arr[2]
は運命共同体で、全く同じ値を指し示しています。
この状態で $a = 10
としているので、$a
と $arr[2]
が指し示している値は (int)10
になります。
最後に var_dump($arr)
で表示すると下記のようになります。
array(3) {
[0] => int(2)
[1] => int(4)
[2] => &int(10)
}
今回は foreach
の仮変数 $a
を参照にしており、foreach
が終了しても、その後の $a
の記述にも影響しました。
foreach
の後に unset() で変数を破棄すれば、その時点で参照ではなくなりますので、その後の記述には影響しません。
$arr = [1,2,3];
foreach ($arr as &$a) {
$a = $a * 2;
}
unset($a); // 変数 $a の破棄
$a = 10;
var_dump($arr);
最後に var_dump($arr)
で表示すると下記のようになります。
array(3) {
[0] => int(2)
[1] => int(4)
[2] => int(6)
}
unset($a)
で変数が破棄されて参照ではなくなったので、その後の $a = 10
は影響しません。
ちなみに、変数が破棄されるのは unset()
内に記述した変数のみになります。
$a = 1;
$b = &$a;
unset($b);
echo $a; // 1
echo $b; // Notice: Undefined variable
$b
に $a
を参照代入しており、$a
と $b
は全く同じ値を指し示していますが、unset($b)
と記述して変数が破棄されるのは $b
のみです。
$a
が破棄されることはありません。
$a
は破棄されていないため、echo $a
で 1
と表示されますが、$b
は破棄されているため、echo $b
とするとエラーが表示されます。
もう1つ foreach
と参照のパターンを見てみましょう。
サンプルケース2(foreachと参照)
$arr = [1,2,3];
foreach ($arr as &$a) {
}
var_dump($arr); // 1回目のvar_dump
foreach ($arr as $a) {
}
var_dump($arr); // 2回目のvar_dump
2つの foreach
がありますが、foreach
内では何の処理もしていません。
このコードは下記の記事からの引用です。
引用元:foreachの$valueを参照で受けると思わぬバグを引き起こす - ぱせらんメモ
1回目の var_dump($arr)
の結果です。
array(3) {
[0] => int(1)
[1] => int(2)
[2] => &int(3)
}
2回目の var_dump($arr)
の結果です。
array(3) {
[0] => int(1)
[1] => int(2)
[2] => &int(2)
}
今回のコードを foreach
を使わずに記述すると下記のようになります。
$arr = [1,2,3];
// 1回目の foreach の処理
$a = &$arr[0]; // 1週目
$a = &$arr[1]; // 2週目
$a = &$arr[2]; // 3週目
var_dump($arr);
// 2回目の foreach の処理
$a = $arr[0]; // 1週目
$a = $arr[1]; // 2週目
$a = $arr[2]; // 3週目
var_dump($arr);
それでは、1行ずつ見ていきましょう。
1行目
$arr = [1,2,3];
変数名 $arr
は (array)
を指し示し、配列の各キーは各値を指し示します。
2行目(最初のforeachの1週目)
$a = &$arr[0]; // 1週目
$a
に $arr[0]
を参照代入しています。
$a
と $arr[0]
は運命共同体になり、全く同じ値を指し示します。
(int)1
の refcount
は 2
になり、is_ref
は 1
になります。
3行目(最初のforeachの2週目)
$a = &$arr[1]; // 2週目
$a
に $arr[1]
を参照代入しています。
$a
と $arr[1]
は運命共同体になり、全く同じ値を指し示します。
(int)2
の refcount
は 2
になり、is_ref
は 1
になります。
それにあわせて (int)1
の refcount
は 1
になり、is_ref
は 0
になります。
4行目(最初のforeachの3週目)
$a = &$arr[2]; // 3週目
$a
に $arr[2]
を参照代入しています。
$a
と $arr[2]
は運命共同体になり、全く同じ値を指し示します。
(int)3
の refcount
は 2
になり、is_ref
は 1
になります。
それにあわせて (int)2
の refcount
は 1
になり、is_ref
は 0
になります。
これで最初の foreach
の処理は終了です。
foreach
内で値を変更することはしていないので、foreach
の処理をしても refcount
と is_ref
しか変わっていません。
注目すべきは、最後の状態です。
先ほども説明しましたが、foreach
の仮変数 $a
は、グローバルスコープにあり、foreach
が終了しても最後の状態のままです。
$a
と $arr[2]
は運命共同体であり、全く同じ値の (int)3
を指し示しています。
この状態で次の foreach
の処理がされます。
5行目(次のforeachの1週目)
$a = $arr[0]; // 1週目
$a
に $arr[0]
の値を代入しています。
$arr[0]
の値は (int1)
ですので、$a
が指し示している値は (int)1
になります。
6行目(次のforeachの2週目)
$a = $arr[1]; // 2週目
$a
に $arr[1]
の値を代入しています。
$arr[1]
の値は (int2)
ですので、$a
が指し示している値は (int)2
になります。
7行目(次のforeachの3週目)
$a = $arr[2]; // 3週目
$a
に $arr[2]
の値を代入しています。
$arr[2]
の値は (int2)
ですので、$a
が指し示している値は (int)2
になります。
最後に var_dump($arr)
を表示した結果です。
array(3) {
[0] => int(1)
[1] => int(2)
[2] => &int(2)
}
1行ずつ見ていくことで、なぜこのような結果になるのか理解できたかと思います。
foreach
の仮変数はグローバルスコープにあり、参照した場合は foreach
が終了した後も影響することに注意してください。
変数を破棄するには unset
を使うようにしましょう。
そもそも、foreach
に参照を使うのはあまり推奨されないやり方です。
そのことに関しては最後に参考になる記事を紹介します。
次はオブジェクト型の参照代入について見ていきましょう。
オブジェクト型の参照代入
オブジェクト型を参照代入すると、PHP内部ではどのように処理されるのか見ていきましょう。
サンプルケース1(オブジェクト型の参照代入)
class foo {
public $bar = 1;
public $baz = "a";
}
$a = new foo;
$b = &$a;
$a->bar = 2;
$b->baz = "b";
$c = &$a->bar;
$c = 4;
$d = $b;
$d->bar = 5;
$d = 6;
$e = clone $b;
$e->bar = 7;
PHP内部ではどのようになっているのか1行ずつ見ていきましょう。
1行目
$a = new foo;
$a
は (object)foo
を指し示します。
(object)foo
の各プロパティが各値を指し示します。
2行目
$b = &$a;
$b
に $a
を参照代入しています。
$b
と $a
は運命共同体になり、全く同じオブジェクトを指し示すようになります。
(object)foo
の refcount
は 2
になり、is_ref
は 1
になります。
3行目
$a->bar = 2;
$a
のオブジェクトの bar
プロパティは (int)2
を指し示すようになります。
それにあわえて (int)1
の refcount
は 1
になります。
4行目
$b->baz = "b";
$b
のオブジェクトの baz
プロパティは (string)'b'
を指し示すようになります。
それにあわえて (string)'a'
の refcount
は 1
になります。
5行目
$c = &$a->bar;
$c
に $a
が指し示しているオブジェクトの bar
プロパティの値を参照代入しています。
$c
と $a->bar
は運命共同体になり、全く同じ値を指し示すようになります。
(int)2
の refcount
は 2
になり、is_ref
は 1
になります。
6行目
$c = 4;
$c
が指し示してる値は (int)4
になります。
7行目
$d = $b;
$d
に $b
のオブジェクトを代入しています。
$d
は $b
と同じオブジェクトを指し示します。
前回の記事でも説明しましたが、オブジェクトの各プロパティの値に変更があっても、オブジェクト自体が複製されることはありません。
オブジェクトを複製するには clone
キーワードを使う必要があります。
$d
は (object)foo
を直接指し示しており、$d = 1
のように別の値を代入するなど、変数名 $d
が直接指し示すものに変更があれば、その時点で複製されて書き換えが行なわれます。
8行目
$d->bar = 5;
$d
が指し示しているオブジェクトの bar
プロパティに 5
を代入しています。
(object)foo
の bar
プロパティが指し示している値は 5
になります。
9行目
$d = 6;
$d
に 6
を代入しています。
$d
が直接指し示しているものに変更があったので、この時点で複製されて書き換えが行なわれます。
$d
は (int)6
を指し示します。
それにあわせて (object)foo
の refcount
は 2
になります。
10行目
$e = clone $b;
$b
が指し示しているオブジェクトの複製を $e
に代入しています。
$e
は複製したオブジェクトを指し示します。
コピーオンライトのため、各プロパティが指し示している値に変更がなければ、既存の値を指し示します。
そのため、$e
のオブジェクトの baz
プロパティは、$b
のオブジェクトの baz
プロパティと同じ (string)'b'
を指し示します。
どちらかの値に変更があった時点で、複製されて書き換えが行なわれます。
ここで注目すべきは、$e
のオブジェクトの bar
プロパティです。
$b
のオブジェクトを複製して、$e
に代入しましたが、もともと $b
の bar
プロパティの値は参照されている状態です。
オブジェクトを複製しても参照の状態は変わりません。
$a
と $b
が指し示している (objcet)foo
の bar
プロパティと $c
と $e
が指し示している (objcet)foo
の bar
プロパティの3つは運命共同体であり、全く同じ値を指し示しています。
オブジェクトの複製にあわせて (int)5
と (string)'b'
の refcount
の数も変わります。
11行目
$e->bar = 7;
$e
が指し示している (object)foo
の bar
プロパティが指し示している値は (int)7
になります。
つまり $a
と $b
が指し示している (object)foo
の bar
プロパティと、$c
と、$e
が指し示している (object)foo
の bar
プロパティが指し示している値は (int)7
になるということです。
オブジェクトの参照代入がどのようになるか理解できたかと思います。
最後に
1行ずつPHP内部でどのように処理されているのか見てきたため、参照の代入について理解できたかと思います。
最後に参照について @tadsan さんの記事を抜粋して紹介します。
リファレンス (references, 参照とも呼ばれる)はPHPの言語仕様の中では珍しく、説明がめんどくさい方の文法です。用語がいっぱい出てきてめんどくさいってことは、 必要に迫られなければ利用しなくて良い 文法だってことです。
私の持論ですが、 リファレンス(参照)を利用しないで済むなら避けた方が賢明 です。
今まで見てきたのでわかると思いますが、参照を使ったときの処理は複雑です。
参照はなるべく避けた方が賢明なようです。
参照の注意点については @tadsan さんの記事をご覧ください。
意図的に参照は避けるにしても、参照についての理解は必要かと思いましたので、今回記事にまとめました。
参照については、参照代入の他に参照渡しがあります。
参照渡しについては別の記事で説明したいと思います。
参考サイト
この記事を書くにあたり、参考にしたサイトです。
記事内で紹介・引用したサイトは既に記載しているためここでは省略します。
とても参考になりました。ありがとうございます。
- How PHP manages variables - entwickler.de
- Алгоритм наследования классов на PHP Компьютерная помощь
- Zoom on PHP objects and classes (PHP 5)
- メモリ管理 — PHP Internals Book 日本語訳
- PHP5 のオブジェクトに関するよくある間違いとメモリ管理 (コピーオンライト) とオブジェクトの取り扱い - Web/DB プログラミング徹底解説
- PHPの参照はCのポインタとは別物
- A Close Look Into PHP Zval - Jiajun Yao
- 【PHP7内核剖析】3.4 面向对象-对象的实现 - 程序园
- PHP 参照について 1 | DinnerPHP
- PHP7における内部値の表現―パート1 : PHP5とPHP7のzvalの仕組み | プログラミング | POSTD
- PHP内核探索之变量(2)-理解引用 - ohmygirl - 博客园
- Алгоритм наследования классов на PHP Компьютерная помощь
- How PHP manages variables - entwickler.de
- PHPの参照と関数についてちゃんと勉強してみる。 - 感謝のプログラミング 10000時間
- PHPのリファレンス(参照)について、自分なりにかみくだいてみる - 風柳メモ
- [PHP] foreach内で参照渡しをしたときに、参照が死なずに残ってしまうのはなぜか - 物欲にまみれたにしふなばし
- PHP foreachでの参照渡しに潜む罠
- Qiita - PHPのforeachで参照渡しをしたいときに気をつけること
- Qiita - PHPのforeachで参照渡しを使ったときの落とし穴
- Qiita - PHPの参照について
- Qiita - phpの参照渡しはメモリを食うって本当?
- &を使ったときの挙動 -<?php$array = array(1,2,3);var_dump($array);e- PHP | 教えて!goo
note
note でも記事を公開してるので、興味がある方はご覧ください。