続いて、「__call()」「__callStatic()」を見ていきましょう。
この二つはアクセス不能メソッドを実行しようとしたときに呼ばれます。インスタンスからの実行であれば__call()が、クラス名からの、静的コンテキストでの実行であれば__callStatic()が呼ばれます。
__get、__setと同様、マジックメソッドが存在しないコードとマジックメソッドが呼ばれるコードを見てみましょう。
まずは__call()と__callStatic()が存在しないコードです(リスト7)。
<?php // class hoge { } // $obj = new hoge(); $obj->func(); hoge::func_static();
実行すると、「Fatal error: Call to undefined method hoge::func()」「Fatal error: Call to undefined method hoge::func_static()」というエラーが出ます。
では、ここにマジックメソッドを追加してみましょう(リスト8の4〜11行目)。
<?php // class hoge { public function __call($name, $args) { echo "call {$name}\n"; var_dump($args); } public static function __callStatic($name, $args) { echo "callStatic {$name}\n"; var_dump($args); } } // $obj = new hoge(); $obj->func(); hoge::func_static();
実行すると、下記のようになります。
call func array(0) { } callStatic func_static array(0) { }
引数を渡した場合、引数は全て、第二引数に配列で入ってきます。簡単に確かめてみましょう。
<?php // class hoge { public function __call($name, $args) { echo "call {$name}\n"; var_dump($args); } public static function __callStatic($name, $args) { echo "callStatic {$name}\n"; var_dump($args); } } // $obj = new hoge(); $obj->func(1, 2, 'abc', array(1,2,3)); hoge::func_static(1, 2, 'abc', array(1,2,3));
実行すると、結果9のようになります。
call func array(4) { [0]=> int(1) [1]=> int(2) [2]=> string(3) "abc" [3]=> array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } } callStatic func_static array(4) { [0]=> int(1) [1]=> int(2) [2]=> string(3) "abc" [3]=> array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } }
__callや__callStaticは、例えば「モックオブジェクト(ユニットテストなどで使われる、スタブ(下位モジュールの代用品)の一種)」を作るときなどに便利です。それ以外ですと、__callを使って比較的手軽にアクセサーを作ることも可能です。
細かい話ですが__callや__callStaticを使うと、ちゃんとプロパティとメソッドを個々に書くのと比較して若干、処理が重くなります。一方で、特に開発序盤〜中盤などでプロパティの増減が激しいときなど、「いったん、__callを使って実装して、ある程度仕様が落ち着いてから書き直す」などの方法も採れますので、手法として覚えておいて損はないでしょう。
実装例としてリスト10を書いてみますので、参考にしていただければと思います。
<?php // class hoge { public function __construct() { $this->data_ = array(); // 使いたいプロパティ名の設定 $this->propertys_ = array ( 'foo' => 1, 'bar' => 1, 'data' => 1, ); } // 疑似アクセサー用の__call() public function __call($name, $param) { if ( (0 === strncmp($name, 'get', 3))||(0 === strncmp($name, 'set', 3)) ) { $type = substr($name, 0, 3); $k = strtolower(substr($name, 3)); if (false === isset($this->propertys_[$k])) { // XXX 適宜エラー処理 throw new Exception('えらー'); } // else if ('set' === $type) { $this->data_[$k] = $param[0]; } else { return $this->data_[$k]; } } else { // XXX 適宜エラー処理 throw new Exception('えらー'); } } //private: private $propertys_; private $data_; } // $obj = new hoge(); $obj->setData(100); echo $obj->getData() , "\n"; // //$obj->setTest(100); // コメントを外すとエラー
後は「$this->propertys_」のkeyに使いたい変数名を追加すると、疑似的にですがアクセサーが自動で追加されていきます。これに、前述の__setと__getを使って、存在しないプロパティへのアクセスを防御する方法を組み合わせると、割と安全な基底クラスを作成できます。
こういった手法を覚えておくと、特に、ちょっと時間がタイトな開発などで、役に立つでしょう。
Copyright © ITmedia, Inc. All Rights Reserved.