PHPにおけるインスタンスの永続化と参照渡し、コピーPHPオブジェクト指向プログラミング入門(終)(2/3 ページ)

» 2015年12月24日 05時00分 公開
[古庄道明株式会社格子組]

インスタンスと「参照」について

 大抵の言語でも同様ですが、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); // 変化する
リスト5
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)
}
結果5

 配列は、変数の中に「値」が入っています。そのために、呼び元の「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変数が示すインスタンスの状態が、変更されることになります。

 この辺りの「参照」という考え方は、特に中級レベル以降非常に重要になるので、いささか難しいかもしれませんが、繰り返しコードを読んだり書いたり変更したりして、自分なりに理解できるところまでかみ砕いておくといいでしょう。

インスタンスをコピーする「clone」

 先ほどの「参照」を踏まえて、インスタンスのコピーについて学んでいきましょう。

 まずは、前提として「配列のコピー」を確認してみます。

<?php
//
$awk = array(1);
$awk2 = $awk;
var_dump($awk);
var_dump($awk2);
//
$awk2[] = 999; // 片方だけ値を変更
var_dump($awk);
var_dump($awk2);
リスト6
array(1) {
  [0]=>
  int(1)
}
array(1) {
  [0]=>
  int(1)
}
array(1) {
  [0]=>
  int(1)
}
array(2) {
  [0]=>
  int(1)
  [1]=>
  int(999)
}
結果6

 通常の数値や文字と同様、代入式を使って普通にコピーできます。しかし、インスタンスでこれをやると、間違いが起きます。

<?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);
リスト7
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)
}
結果7

 $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);
リスト8
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)
}
結果8

 var_dumpで、$obj2のインスタンスが「#2」になっています(結果8の11、15行目)。つまり「#1」とは異なる、別のインスタンスになったことが分かります。

 このように、cloneを使うことで、インスタンスは初めて「コピー」できるようになります。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。