便利だけど使いどころが難しいPHPの代表的なマジックメソッドと無名関数の使い方PHPオブジェクト指向プログラミング入門(4)(2/3 ページ)

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

アクセス不能メソッドを実行する__call()と__callStatic()の使い方

 続いて、「__call()」「__callStatic()」を見ていきましょう。

 この二つはアクセス不能メソッドを実行しようとしたときに呼ばれます。インスタンスからの実行であれば__call()が、クラス名からの、静的コンテキストでの実行であれば__callStatic()が呼ばれます。

 __get、__setと同様、マジックメソッドが存在しないコードとマジックメソッドが呼ばれるコードを見てみましょう。

 まずは__call()と__callStatic()が存在しないコードです(リスト7)。

<?php
//
class hoge {
}
//
$obj = new hoge();
$obj->func();
hoge::func_static();
リスト7

 実行すると、「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();
リスト8

 実行すると、下記のようになります。

call func
array(0) {
}
callStatic func_static
array(0) {
}
結果8

 引数を渡した場合、引数は全て、第二引数に配列で入ってきます。簡単に確かめてみましょう。

<?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

 実行すると、結果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)
  }
}
結果9

 __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); // コメントを外すとエラー
リスト10

 後は「$this->propertys_」のkeyに使いたい変数名を追加すると、疑似的にですがアクセサーが自動で追加されていきます。これに、前述の__setと__getを使って、存在しないプロパティへのアクセスを防御する方法を組み合わせると、割と安全な基底クラスを作成できます。

 こういった手法を覚えておくと、特に、ちょっと時間がタイトな開発などで、役に立つでしょう。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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