PHPにおけるインスタンスの永続化と参照渡し、コピー:PHPオブジェクト指向プログラミング入門(終)(2/3 ページ)
「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する連載。最終回は、「永続化インスタンス」の必要性とserialize、unserialize関数、インスタンスと「参照」、「浅い」コピーと「深い」コピー、cloneキーワード、マジックメソッド「__sleep()」「__wakeup()」「__clone」などについて解説。
インスタンスと「参照」について
大抵の言語でも同様ですが、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変数が示すインスタンスの状態が、変更されることになります。
この辺りの「参照」という考え方は、特に中級レベル以降非常に重要になるので、いささか難しいかもしれませんが、繰り返しコードを読んだり書いたり変更したりして、自分なりに理解できるところまでかみ砕いておくといいでしょう。
インスタンスをコピーする「clone」
先ほどの「参照」を踏まえて、インスタンスのコピーについて学んでいきましょう。
まずは、前提として「配列のコピー」を確認してみます。
<?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.
関連記事
- 初心者がPHPプログラミングを始めるための基礎知識とXAMPPのインストール
オープンソースのWeb開発向けスクリプト言語「PHP」の文法を一から学ぶための入門連載。初回は、PHPの概要や特徴を紹介し、環境構築や「Hello World!」までの手順などを解説します【PHP 7.1含め2017年の情報に合うように対応、XAMPP環境構築を追記】。 - PHP編に突入! まずはソースをダウンロード
今回からPHPのビルドとインストールに入ります。まずはPHPという言語の概要と、ソースコードの入手法を解説します(編集部) - 理論編:PHPについて知ろう
スクリプトを記述するだけの簡単なプログラミングで、Web対応の高速なデータベースアプリケーションを実現する手法としてPHPが急速に注目を集めている。しかもデータベースはオープンソースのPostgreSQLだけでなく、Oracleなどの商用データベースも扱える。ここでは、5月にバージョンアップしたばかりのPHP4によるWeb-DBシステム構築法を紹介しよう。 - Mac OS X+PHPでオールインワン環境(インストール編):Mac内にPHP4、5、6を同居させるコツ
PHP4の開発は終了したが、移行の問題は残されている。異なるバージョンのPHPをスムーズに切り替えるには? - @IT自分戦略研究所 資格辞典:PHP技術者認定試験
- 自分をプログラミング言語に例えると何?
2013年1月16日、エンジニアたちが集うトークライブイベント「TechLION vol.11」が開催された。今回のレポートでは、「自分をプログラミング言語に例える」というお題に答えた5人の技術者の回答をみていく。