「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する連載。最終回は、「永続化インスタンス」の必要性とserialize、unserialize関数、インスタンスと「参照」、「浅い」コピーと「深い」コピー、cloneキーワード、マジックメソッド「__sleep()」「__wakeup()」「__clone」などについて解説。
「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する本連載。今までは下記のように、PHPを例としたオブジェクト指向について学んできました。
今回は、いよいよ最終回。インスタンスの保存の仕方やコピーの仕方などを解説します。
プログラム中で作成したインスタンスは、当然ですが「プログラムの終了」とともにメモリから解放されて、霧散します。通常はそれでもよいのですが、インスタンスによっては「情報を保持して、持ち回りたい」ものも少なからず存在します。
例えばECサイト作成時の「買い物かごクラス」は可能であれば「(購入が終わるまでは)持ち回りたい」インスタンスでしょうし、ゲーム作成中の「敵を意味するインスタンス」もまた、state(状態:典型的には、残HPなど)などを保持している観点から「『敵が倒れるまでは持ち回りたい』インスタンスである」といえます。
「永続インスタンス」というような呼称で、Javaの世界などでは、割と以前からこの「持ち回りたいインスタンス」を扱ってきました。ニーズとしては同様のものがあることから、PHPでも、「serialize()」「unserialize()」関数を使って、そのようなことが可能になります。
まずは、serialize()、unserialize()の実装を確認してみましょう。
<?php class hoge { public $i_; public $j_; } // $obj = new hoge(); $obj->i_ = 1; $obj->j_ = 10; $s = serialize($obj); var_dump($s); $obj2 = unserialize($s); var_dump($obj2);
serialize関数によって、インスタンスが「一本の文字列」になります。文字列になるために、例えば「DBに保存する」などを含めて、情報の保持が極めて容易になります。また、その文字列をunserialize関数に渡すと、元の状態(インスタンス)に戻ります。
なおserialize関数は、インスタンスだけではなく配列も扱えます。
<?php // $awk = array(1,2,3); $s = serialize($awk); var_dump($s); $awk2 = unserialize($s); var_dump($awk2);
あまりむやみにインスタンスを永続化すると情報の保持にコストが掛かってしまいますが、適切な箇所で扱うと大変に便利なのも事実です。特に「状態を扱うインスタンス」は、比較的、永続化するのに都合が良い場合が多いので、こういった手法を少しでも知っておくと、設計や実装の幅が広がるでしょう。
便利なserializeですが、「保存ができない値」があります。
一つは「リソース型」と言われる変数型の値で、典型的にはDBハンドルやファイルハンドルが該当します。また「保存可能な値」であっても、「特に保存をする必要がない値」である場合も存在すると思います。そのような場合の中でも、時には「serializeの対象にしたいプロパティ」「unserializeのときに復元したい値」を恣意的に操作したいケースがあります。
そのような状況で、「__sleep()」「__wakeup()」を使うとよいでしょう。
まず、__sleep()マジックメソッドの使い方を見てみます。
<?php class hoge { // public function __sleep() { return array('i_', 'j_'); } // public $i_; public $j_; public $k_; } // $obj = new hoge(); $obj->i_ = 1; $obj->j_ = 10; $obj->k_ = 100; var_dump($obj); $s = serialize($obj); var_dump($s); $obj2 = unserialize($s); var_dump($obj2);
object(hoge)#1 (3) { ["i_"]=> int(1) ["j_"]=> int(10) ["k_"]=> int(100) } call __sleep() string(42) "O:4:"hoge":2:{s:2:"i_";i:1;s:2:"j_";i:10;}" object(hoge)#2 (3) { ["i_"]=> int(1) ["j_"]=> int(10) ["k_"]=> NULL }
__sleepは、serialize関数が呼ばれるタイミングで自動的にcallされます。また、__sleepは「その戻り値として返したプロパティ名だけをserializeするようにserialize関数に依頼をする」機能があります。そのため、結果3の場合、「$k_」だけが「シリアライズされず」に復元されています(17行目)。
このように、__sleepを使うことで、シリアライズできないリソース型の変数や、シリアライズ不要な変数を、シリアライズの対象外にするように働き掛けることができます。
unserialize関数が呼ばれるタイミングで起動するのが、__wakeupです。もう少し厳密には「unserialize関数が実行された直後」に呼ばれます。そのため、「unserialize関数によって復元した値を使う」ことも可能です。
実装を見てみましょう。
<?php class hoge { // public function __sleep() { echo "call __sleep()\n"; return array('i_', 'j_'); } public function __wakeup() { echo "call __wakeup()\n"; $this->k_ = $this->i_ + $this->j_; } // public $i_; public $j_; public $k_; } // $obj = new hoge(); $obj->i_ = 1; $obj->j_ = 10; $obj->k_ = 100; var_dump($obj); $s = serialize($obj); var_dump($s); $obj2 = unserialize($s); var_dump($obj2);
object(hoge)#1 (3) { ["i_"]=> int(1) ["j_"]=> int(10) ["k_"]=> int(100) } call __sleep() string(42) "O:4:"hoge":2:{s:2:"i_";i:1;s:2:"j_";i:10;}" call __wakeup() object(hoge)#2 (3) { ["i_"]=> int(1) ["j_"]=> int(10) ["k_"]=> int(11) }
このように「内部の値を再計算する」「DBハンドルなどを再取得する」などの使われ方が多いと思います。この2つのマジックメソッドを使うことで、serializeやunserializeを「より自在に」使えるようになります。
Copyright © ITmedia, Inc. All Rights Reserved.