PHPにおけるインスタンスの永続化と参照渡し、コピー:PHPオブジェクト指向プログラミング入門(終)(3/3 ページ)
「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する連載。最終回は、「永続化インスタンス」の必要性とserialize、unserialize関数、インスタンスと「参照」、「浅い」コピーと「深い」コピー、cloneキーワード、マジックメソッド「__sleep()」「__wakeup()」「__clone」などについて解説。
「浅い」コピーと「深い」コピー
ここまでインスタンスのコピーを見てきましたが、実はまだ、問題が隠れています。
まずは、問題のあるコードから見ていきましょう。
<?php // class hoge { public $i_; public $obj_; } class foo { public $j_; } // $obj = new hoge(); $obj->i_ = 1; $obj->obj_ = new foo(); $obj->obj_->j_ = 20; $obj2 = clone $obj; // 代入ではなくcloneを使う var_dump($obj); var_dump($obj2); // $obj2->i_ = 999; // 片方だけ値を変更 $obj2->obj_->j_ = 999; var_dump($obj); var_dump($obj2);
object(hoge)#1 (2) { ["i_"]=> int(1) ["obj_"]=> object(foo)#2 (1) { ["j_"]=> int(20) } } object(hoge)#3 (2) { ["i_"]=> int(1) ["obj_"]=> object(foo)#2 (1) { ["j_"]=> int(20) } } object(hoge)#1 (2) { ["i_"]=> int(1) ["obj_"]=> object(foo)#2 (1) { ["j_"]=> int(999) } } object(hoge)#3 (2) { ["i_"]=> int(999) ["obj_"]=> object(foo)#2 (1) { ["j_"]=> int(999) } }
$objには、#1、$obj2には#3のインスタンスが入っているので、プロパティ$i_の値は適切に分離ができています。しかし、プロパティ$obj_の中にあるインスタンスは、#1の中も#3の中も、どちらも#2のインスタンスが入ってしまっています(結果9の25、34行目が同じ)。「$obj2->obj_->j_ = 999;」(リスト9の20行目)で片方のインスタンスだけ値を変更したいのに、#1の中の$obj_も、引きずられるように値が変更されてしまっているのです。
言い方を変えると、#1は適切にコピー(clone)されていますが、#1の中にあるインスタンスは「コピー(clone)されていない、中途半端な状態になっている」といえます。
こういった状態を「浅いコピー(シャローコピー:Shallow Copy)」と言って、オブジェクト指向プログラミングでは、言語を問わず出て来る概念です。もちろん「意図してこの状態にしている」のであればいいのですが、大抵の場合はそうではないので、より適切に「深いコピー(ディープコピー:Deep Copy)」を行う必要があることが多いといわれています。
マジックメソッド「__clone」で「深いコピー」
PHPでは、こんなときにもマジックメソッドを用います。「__clone」というマジックメソッドは「cloneされた直後」に暗黙的に呼び出されるので、この__cloneを使って「深いコピー」を実装します。
では、実装例を見ていきましょう。
<?php // class hoge { // public function __clone() { echo "call __clone()\n"; $this->obj_ = clone $this->obj_; // プロパティのうち「インスタンス型のもの」は全てcloneしておく } // public $i_; public $obj_; } class foo { public $j_; } // $obj = new hoge(); $obj->i_ = 1; $obj->obj_ = new foo(); $obj->obj_->j_ = 20; $obj2 = clone $obj; // 代入ではなくcloneを使う var_dump($obj); var_dump($obj2); // $obj2->i_ = 999; // 片方だけ値を変更 $obj2->obj_->j_ = 999; var_dump($obj); var_dump($obj2);
call __clone() object(hoge)#1 (2) { ["i_"]=> int(1) ["obj_"]=> object(foo)#2 (1) { ["j_"]=> int(20) } } object(hoge)#3 (2) { ["i_"]=> int(1) ["obj_"]=> object(foo)#4 (1) { ["j_"]=> int(20) } } object(hoge)#1 (2) { ["i_"]=> int(1) ["obj_"]=> object(foo)#2 (1) { ["j_"]=> int(20) } } object(hoge)#3 (2) { ["i_"]=> int(999) ["obj_"]=> object(foo)#4 (1) { ["j_"]=> int(999) } }結果10
__cloneが呼び出されていること。その中でcloneした結果として、#1の中のobj_と、#3の中のobj_の中にあるインスタンスが、それぞれ「別のもの(#2と#4)」になっていること(結果10の26、35行目が異なる)。その結果として「$obj2->obj_->j_ = 999;」(リスト10の26行目)の操作が、$objに影響していないことが分かると思います。
「インスタンスをコピーしたい」というケースはあまりありません。しかし「必要になった」ときに、こういった知識の有無が業務の速度に大きく影響することがあるので、「浅いコピー」「深いコピー」という考え方を、しっかりと理解しておくとよいでしょう。
本連載のまとめ
全6回にわたって、「PHPにおけるオブジェクト指向」の「比較的よくある」話をいくつかしました。その中でも「他言語でも一般的に通用する内容」「PHPに比較的固有なお話」がありました。この2つは、どちらも大切でバランス良く学習していくといいでしょう。特に、プログラミング言語固有ではない「プログラミングの知識」は中級プログラマーの頃に意識する必要があると思います。
またどこかでチャンスがありましたら、PHPの「少しだけ濃い、深い辺り」に踏み込んだ記事を皆さまにお届けできればと思っております。それでは、全6回をご愛読いただきありがとうございました。本稿がプログラマーの皆さまの参考になれば、望外の幸いです。
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人の技術者の回答をみていく。