大抵の言語でも同様ですが、PHPのインスタンスは、正確には「インスタンスへの参照」の値を持っています。これと対照的なものとして、配列は「参照ではない」のです。
この2つを、まずコードによって簡単に確認していきましょう。
<?php // class hoge { public $i_; } // function foo($awk) { $awk[] = 10; } function bar($obj) { $obj->i_ = 999; } // $array_data = array(1,2,3); var_dump($array_data); foo($array_data); var_dump($array_data); // 変化しない // $ins = new hoge(); $ins->i_ = 1; var_dump($ins); bar($ins); var_dump($ins); // 変化する
array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } object(hoge)#1 (1) { ["i_"]=> int(1) } object(hoge)#1 (1) { ["i_"]=> int(999) }
配列は、変数の中に「値」が入っています。そのために、呼び元の「foo($array_data);」(リスト5の15行目)で渡した値は、コピーされて「function foo($awk)」に渡ります(厳密には違うのですが、いったん、このように理解する方が早いので、このまま進めます。深いレイヤーに興味があるときは、「copy on write」について調べてみるとよいでしょう)。
関数fooの中の変数の値の操作は「関数fooのlocalスコープでのみ有効な値」であるために、値の操作は、関数fooを抜けた瞬間に「変数$awkが破棄される」ことで事実上「なかった」ことになります。結果として、グローバルスコープにある$array_dataの値は「何も変化しない」ので、このような結果になります(結果5の9〜16行目)。
一方で、インスタンスは「インスタンスへの参照」という値が入っています。var_dumpで出てくる「object(hoge)#1」の、シャープの後の数字を見ると分かりやすいですね(結果5の17行目)。今回の場合は「#1インスタンスへの参照」の値が、グローバルスコープの$ins変数の中に入っています。
関数barが呼ばれたとき(リスト5の23行目)は「グローバルスコープの$ins変数とは異なる値」です(結果5の23行目)が、中身としては「#1インスタンスへの参照」です(結果5の21行目)。ローカルスコープの変数$objに対する「$obj->i_ = 999;」(リスト5の11行目)という操作は、そのまま「#1インスタンスに対する操作」となるために、グローバルスコープの$ins変数が示すインスタンスの状態が、変更されることになります。
この辺りの「参照」という考え方は、特に中級レベル以降非常に重要になるので、いささか難しいかもしれませんが、繰り返しコードを読んだり書いたり変更したりして、自分なりに理解できるところまでかみ砕いておくといいでしょう。
先ほどの「参照」を踏まえて、インスタンスのコピーについて学んでいきましょう。
まずは、前提として「配列のコピー」を確認してみます。
<?php // $awk = array(1); $awk2 = $awk; var_dump($awk); var_dump($awk2); // $awk2[] = 999; // 片方だけ値を変更 var_dump($awk); var_dump($awk2);
array(1) { [0]=> int(1) } array(1) { [0]=> int(1) } array(1) { [0]=> int(1) } array(2) { [0]=> int(1) [1]=> int(999) }
通常の数値や文字と同様、代入式を使って普通にコピーできます。しかし、インスタンスでこれをやると、間違いが起きます。
<?php // class hoge { public $i_; } // $obj = new hoge(); $obj->i_ = 1; $obj2 = $obj; var_dump($obj); var_dump($obj2); // $obj2->i_ = 999; // 片方だけ値を変更 var_dump($obj); var_dump($obj2);
object(hoge)#1 (1) { ["i_"]=> int(1) } object(hoge)#1 (1) { ["i_"]=> int(1) } object(hoge)#1 (1) { ["i_"]=> int(999) } object(hoge)#1 (1) { ["i_"]=> int(999) }
$obj2だけに変更を加えているはずですが、$objの方まで値が変わってしまっています(結果7の10行目)。これは、$obj(および$obj2)に入っている値が「インスタンス」ではなく、正確には「インスタンス#1への参照」だからです。
そのために、$objであっても$obj2であっても「インスタンス#1への参照」なので、どちらか片方で値を操作すると、それは「インスタンス#1への変更」になるために、どちらの変数にも影響が出てしまいます。
インスタンスを「コピー」するためには、専用の書式があります。その書式「clone」を使って、実装し直してみましょう。
<?php // class hoge { public $i_; } // $obj = new hoge(); $obj->i_ = 1; $obj2 = clone $obj; // 代入ではなくcloneを使う var_dump($obj); var_dump($obj2); // $obj2->i_ = 999; // 片方だけ値を変更 var_dump($obj); var_dump($obj2);
object(hoge)#1 (1) { ["i_"]=> int(1) } object(hoge)#2 (1) { ["i_"]=> int(1) } object(hoge)#1 (1) { ["i_"]=> int(1) } object(hoge)#2 (1) { ["i_"]=> int(999) }
var_dumpで、$obj2のインスタンスが「#2」になっています(結果8の11、15行目)。つまり「#1」とは異なる、別のインスタンスになったことが分かります。
このように、cloneを使うことで、インスタンスは初めて「コピー」できるようになります。
Copyright © ITmedia, Inc. All Rights Reserved.