特殊メソッド/オートロード

PHPには、「__construct()」(コンストラクタ)や「__destruct()」(デストラクタ)の他にも、その名前で定義されたメソッドは“呼ばれ時”になると勝手にコールされるという「特殊メソッド」があります。また「特殊関数」があります。

何れも「__(アンダーライン2本)」で始まるメソッド名または関数名です。PHPでは「__(アンダーライン2本)」で始まるメソッド名または関数名を、“特殊な関数”として予約していて、「__(アンダーライン2本)」で始まる関数名を付けないよう推奨されているので、付けない様にしましょう。

これらの「特殊メソッド・関数」を定義する場合、“引数の数と返り値の型を決められた形態通りに定義”しなくてはなりません。そうしないと機能しないし、コールされる際に「E_ERROR」エラーを発するか、場合によってはサーバがダウンします。

特殊メソッド(上段)/特殊関数(下段)一覧
__call() /  __get() /  __set() /  __toString() /  __clone() /  __sleep() /  __wakeup()
__autoload()
メソッド/関数の“呼ばれ時”
未定義メソッドのコール時 /  未定義プロパティへアクセス時 /  オブジェクトの出力時 /  オブジェクトのクローン生成時 /  オブジェクトのシリアル化/非シリアル化時
クラスが未定義だった時

未定義のメソッドをコールした時にコールされるメソッド

通常、未定義のメソッドをコールすると「E_ERROR」エラーを発して終了しますが、「__call()」メソッドが定義されていると、エラーを発せず、このメソッドがコールされます。

__call()メソッド
mixed __call(string メソッド名, array 引数)
未定義のメソッドをコールした時に代わりにコールされる。
「メソッド名」にコールされたメソッド名が、「引数」にコール時の実引数を格納した配列が、それぞれ渡されます。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class BASE{
        public function __call($m_name, $args){
            echo "****** begin __call() ******\n";
            echo "Call to undefined method ", __CLASS__, "::{$m_name}()\n";
            echo "arguments : \n";
            var_dump($args);
            echo "****** end __call() ******\n\n";
            return false;
        }
    }
    
    class SUB extends BASE{
    }
    
    $sub = new SUB;
    
    #未定義のメソッドをコールしてみる
    $sub->hode()
        or
    $sub->nasu("HODENASU", true, array(123), NULL)
        or
    die("*** die ***");
?>
</pre>
****** begin __call() ******
Call to undefined method BASE::hode()
arguments : 
array(0) {
}
****** end __call() ******

****** begin __call() ******
Call to undefined method BASE::nasu()
arguments : 
array(4) {
  [0]=>
  string(8) "HODENASU"
  [1]=>
  bool(true)
  [2]=>
  array(1) {
    [0]=>
    int(123)
  }
  [3]=>
  NULL
}
****** end __call() ******

*** die ***

未定義のメソッドをコールすると、代わりに「__call()」メソッドがコールされました。

未定義のプロパティを参照しようとした時/値を代入しようとした時にコールされるメソッド

通常、未定義のプロパティを参照しようとすると単に「E_NOTICE」通知を発し(「NULL」を得る)、また未定義のプロパティに値を代入しようとすると、自動的にそのプロパティを定義します。

ですが、「__get()」メソッドが定義されていると、未定義のプロパティを参照しようとした時にこのメソッドがコールされます。また「__set()」メソッドが定義されていると、未定義のプロパティに値を代入しようとした時にこのメソッドがコールされます。

__get()メソッド
mixed __get(string プロパティ名)
未定義のプロパティを参照しようとした時にコールされる。
「プロパティ名」に参照しようとしたプロパティ名が渡されます。
__set()メソッド
void __set(string プロパティ名, mixed 値)
未定義のプロパティに値を代入しようとした時にコールされる。
「プロパティ名」に代入しようとしたプロパティ名が、「値」に代入しようとした値が渡されます。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class BASE{
        private $var = array();
        public function __get($p_name){
            echo "****** begin __get() ******\n";
            echo "Undefined property: ", __CLASS__, "::{$p_name}\n";
            echo "****** end __get() ******\n\n";
            if(isset($this->var[$p_name])){
                echo "   * return {$this->var[$p_name]} *\n\n";
                return $this->var[$p_name];
            }else{
                echo "   * return NULL *\n\n";
                return NULL;
            }
        }
        public function __set($p_name, $value){
            echo "\n****** begin __set() ******\n";
            echo "Undefined property: ", __CLASS__, "::{$p_name}\n";
            echo "value : \n";
            var_dump($value);
            echo "****** end __set() ******\n\n";
            $this->var[$p_name] = $value;
        }
    }

    class SUB extends BASE{
    }
    
    $sub = new SUB;
    
    #未定義のプロパティの参照を試みる
    var_dump($sub->hode);
    
    #未定義のプロパティへの値の代入を試みる
    $sub->hode = array("HODENASU", 123, true);
    
    #未定義のプロパティの参照を試みる
    var_dump($sub->hode);
?>
</pre>
****** begin __get() ******
Undefined property: BASE::hode
****** end __get() ******

   * return NULL *

NULL

****** begin __set() ******
Undefined property: BASE::hode
value : 
array(3) {
  [0]=>
  string(8) "HODENASU"
  [1]=>
  int(123)
  [2]=>
  bool(true)
}
****** end __set() ******

****** begin __get() ******
Undefined property: BASE::hode
****** end __get() ******

   * return Array *

array(3) {
  [0]=>
  string(8) "HODENASU"
  [1]=>
  int(123)
  [2]=>
  bool(true)
}

未定義のプロパティを参照しようとすると「__get()」メソッドが、未定義のプロパティに値を代入しようとすると「__set()」メソッドがコールされました。

オブジェクトを文字列として評価した時にコールされるメソッド

通常、オブジェクトを文字列として評価(要するに文字列として出力)すると、「Object id #1」のようなものを出力しますが、「__toString()」メソッドが定義されていると、「echo()」もしくは「print()」で単独で(余計なものをつけないで)出力しようとした時にコールされます。結果、このメソッドが返した文字列を出力する事になります。

__toString()メソッド
string __toString(void)
オブジェクトを文字列として出力しようとした時にコールされる。
echo()」もしくは「print()」にオブジェクトを与える場合は、余計なものをつけないで単独で与えます。そうしない場合はコールされません。
注:このメソッドの返り値の型は必ず文字列型でなければなりません。それ以外の型の値を返すと場合によって(「echo new HODE;」の様にすると)サーバがダウンします。念の為、返り値を型キャストすると安全です。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class BASE{
        public    $base_pub_var = "BASE public property";
        protected $base_pro_var = "BASE protected property";
        private   $base_pri_var = "BASE private property";
        private function base_pri_method(){
            return false;
        }
        public function __toString(){
            $str = "****** begin __toString() ******\n";
            $str .= "class ".__CLASS__."\n";
            $str .= "\n- object_vars -\n\n";
            foreach(get_object_vars($this) as $var => $val){
                $str .= "{$var} => {$val}\n";
            }
            $str .= "\n- class_vars -\n\n";
            foreach(get_class_vars(__CLASS__) as $var => $val){
                $str .= "{$var} => {$val}\n";
            }
            $str .= "\n- class_methods -\n\n";
            foreach(get_class_methods(__CLASS__) as $method){
                $str .= "{$method}\n";
            }
            $str .= "****** end __toString() ******\n\n";
            return (string)$str; //念の為、型キャストする
        }
    }

    class SUB extends BASE{
        private $sub_pri_var = "SUB private property";
        private function sub_pri_method(){
            return false;
        }
    }
    
    $sub = new SUB;
    
    #オブジェクトにゴミを付けて出力
    echo "".$sub, "\n";
    
    #オブジェクトを単独で出力
    echo "\n", $sub;
?>
</pre>
Object id #1

****** begin __toString() ******
class BASE

- object_vars -

sub_pri_var => SUB private property
base_pub_var => BASE public property
base_pro_var => BASE protected property

- class_vars -

base_pub_var => BASE public property
base_pro_var => BASE protected property
base_pri_var => BASE private property

- class_methods -

base_pri_method
__toString
****** end __toString() ******

echo "\n", $sub;」の部分で「__toString()」メソッドがコールされ、返って来た文字列が出力されました。

clone()」命令でオブジェクトのクローンを生成した時にコールされるメソッド

オブジェクトのクローン(コピー)を生成する時に「clone」を使用しますが、「__clone()」メソッドが定義されていると、「clone」でクローンが生成された時に(クローン側で)コールされます。

__clone()メソッド
void __clone(void)
オブジェクトのクローンを生成した時に(クローン側で)コールされる。クローン側でコールされるので、“クローンのコンストラクタ”と言える。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class BASE{
        private $var;
        private $id;
        public function __construct(){
            $this->var = "HODENASU";
            $this->id  = "original";
            echo "\n[][][] __construct() [][][]\n\n";
        }
        public function __destruct(){
            echo "~~~~~~ __destruct() of {$this->id} ~~~~~~\n\n";
        }
        public function __clone(){
            $this->id = "clone";
            echo "\n[]==[] __clone() []==[]\n\n";
        }
        public function getVars($name){
            echo str_pad('$'.$name.'-&gt;var', 13), ' : ', $this->var, "\n",
                 str_pad('$'.$name.'-&gt;id', 13),  ' : ', $this->id,  "\n\n";
        }
    }

    class SUB extends BASE{
    }
    
    echo '&gt;$sub = new SUB;', "\n";
    $sub = new SUB;
    
    echo '&gt;$sub1 = $sub;', "\n";
    $sub1 = $sub;
    
    echo '&gt;$sub2 =& $sub;', "\n";
    $sub2 =& $sub;
    
    echo '&gt;$sub3 = clone $sub;', "\n";
    $sub3 = clone $sub;
    
    $sub->getVars("sub");
    $sub1->getVars("sub1");
    $sub2->getVars("sub2");
    $sub3->getVars("sub3");
    
    die("****** die ******\n\n");
?>
</pre>
>$sub = new SUB;

[][][] __construct() [][][]

>$sub1 = $sub;
>$sub2 =& $sub;
>$sub3 = clone $sub;

[]==[] __clone() []==[]

$sub->var  : HODENASU
$sub->id   : original

$sub1->var : HODENASU
$sub1->id  : original

$sub2->var : HODENASU
$sub2->id  : original

$sub3->var : HODENASU
$sub3->id  : clone

****** die ******

~~~~~~ __destruct() of original ~~~~~~

~~~~~~ __destruct() of clone ~~~~~~

clone $sub」の部分でオブジェクトのクローンを生成すると、「__clone()」メソッドがコールされました。

__clone()」メソッド内でプロパティを更新しましたが、反映されているのはクローン側のみです。つまり、コールされる「__clone()」メソッドは、オリジナルのオブジェクト側のものではなく生成されたクローン側のものです。「__clone()」メソッドは“クローンのコンストラクタ”と言えます。

オブジェクトのシリアル化/非シリアル化時にコールされるメソッド

オブジェクトの、「serialize()」関数(変数をシリアル化してその文字列を返す関数)によるシリアル化、または「unserialize()」関数(文字列を非シリアル化して復元された値を返す関数)による非シリアル化が行われようとした時に、「__sleep()」メソッドが定義されていると、シリアル化が行われる前にこのメソッドがコールされ、「__wakeup()」メソッドが定義されていると、非シリアル化が行われる前にこのメソッドがコールされます。

__sleep()メソッド
array __sleep(void)
オブジェクトが「serialize()」関数によってシリアル化されようとした時、このメソッドが存在すればシリアル化する前にコールされる。
このメソッドは、“シリアル化させたいプロパティ名を格納した配列を返す”必要があります。返された配列に格納された名前のプロパティのみシリアル化されます。なお、このメソッドが存在しない場合は全てのプロパティがシリアル化されます。
その他、シリアル化されるにあたっての後片付け等の処理を記述する。
__wakeup()メソッド
void __wakeup(void)
シリアル化されたオブジェクトが「unserialize()」関数によって非シリアル化されようとした時、このメソッドが存在すれば非シリアル化後にコールされる。
このメソッドでは、シリアル化される前に有していたリソースの復元等の処理を記述します。

オブジェクトをシリアル化した場合、保存されるのはクラス名とプロパティ名とその値のみなので、非シリアル化する時点で、そのオブジェクトのクラスが定義されている必要があります。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class BASE{
        protected $base_var1  = " - ";
        protected $base_var2  = " - ";
        protected $base_var3s = " - ";
        public function __construct(){
            $this->base_var1  = "HODENASU!!!";
            $this->base_var2  = "HODENASU!!!";
            $this->base_var3s = "HODENASU!!!";
            $this->sub_var1s  = "HODENASU!!!";
            $this->sub_var2   = "HODENASU!!!";
            $this->sub_var3s  = "HODENASU!!!";
        }
        public function __sleep(){
            echo "\n****** zzz __sleep() zzz ******\n\n";
            $p_name_arr = array();
            foreach(array_keys(get_object_vars($this)) as $p_name){
                if($p_name{strlen($p_name) - 1} == "s")
                    array_push($p_name_arr, $p_name);
            }
            return $p_name_arr;
        }
        public function __wakeup(){
            echo "\n******     __wakeup()    ******\n\n";
        }
        public function __toString(){
            echo "*** begin __toString() ***\n";
            print_r(get_object_vars($this));
            echo "*** end __toString() ***\n";
            return "";
        }
    }

    class SUB extends BASE{
        protected $sub_var1s = " - ";
        protected $sub_var2  = " - ";
        protected $sub_var3s = " - ";
    }
    
    class HODE{
        private $hode_var1 = " - ";
        private $hode_var2 = " - ";
        private $hode_var3 = " - ";
        static private $hode_sta_var = " - ";
        public function __construct(){
            $this->hode_var1 = "HODENASU!!!";
            $this->hode_var2 = "HODENASU!!!";
            $this->hode_var3 = "HODENASU!!!";
            self::$hode_sta_var = "HODENASU!!!";
        }
        public function __toString(){
            echo "*** begin __toString() ***\n";
            print_r(get_object_vars($this));
            echo "*** end __toString() ***\n";
            return "";
        }
    }
    
    $sub = new SUB;
    
    echo '&gt;echo $sub;', "\n\n";
    echo $sub;
    
    echo "\n", '&gt;echo serialize($sub);', "\n";
    echo $sub_str = serialize($sub), "\n";
    
    echo "\n", '&gt;echo unserialize($sub_str);', "\n";
    echo $sub = unserialize($sub_str), "\n";
    
    echo "<hr />";
    
    $hode = new HODE;
    
    echo '&gt;echo $hode;', "\n\n";
    echo $hode;
    
    echo "\n", '&gt;echo serialize($hode);', "\n\n";
    echo $hode_str = serialize($hode), "\n";
    
    echo "\n", '&gt;echo unserialize($hode_str);', "\n\n";
    echo $hode = unserialize($hode_str);
?>
</pre>
>echo $sub;

*** begin __toString() ***
Array
(
    [sub_var1s] => HODENASU!!!
    [sub_var2] => HODENASU!!!
    [sub_var3s] => HODENASU!!!
    [base_var1] => HODENASU!!!
    [base_var2] => HODENASU!!!
    [base_var3s] => HODENASU!!!
)
*** end __toString() ***

>echo serialize($sub);

****** zzz __sleep() zzz ******

O:3:"SUB":3:{s:12:"*sub_var1s";s:11:"HODENASU!!!";
s:12:"*sub_var3s";s:11:"HODENASU!!!";s:13:"*base_var3s";
s:11:"HODENASU!!!";}

>echo unserialize($sub_str);

******     __wakeup()    ******

*** begin __toString() ***
Array
(
    [sub_var1s] => HODENASU!!!
    [sub_var2] =>  - 
    [sub_var3s] => HODENASU!!!
    [base_var1] =>  - 
    [base_var2] =>  - 
    [base_var3s] => HODENASU!!!
)
*** end __toString() ***


>echo $hode; *** begin __toString() *** Array ( [hode_var1] => HODENASU!!! [hode_var2] => HODENASU!!! [hode_var3] => HODENASU!!! ) *** end __toString() *** >echo serialize($hode); O:4:"HODE":3:{s:15:"HODEhode_var1";s:11:"HODENASU!!!"; s:15:"HODEhode_var2";s:11:"HODENASU!!!";s:15:"HODEhode_var3"; s:11:"HODENASU!!!";} >echo unserialize($hode_str); *** begin __toString() *** Array ( [hode_var1] => HODENASU!!! [hode_var2] => HODENASU!!! [hode_var3] => HODENASU!!! ) *** end __toString() ***

serialize($sub)」の部分でオブジェクトのシリアル化を試みると、まず「__sleep()」メソッドがコールされました。このメソッドでは、“最後の文字が「s」であるプロパティ名のみ配列に格納”しその配列を返しています。その結果、「__sleep()」メソッドが返した配列に含まれるプロパティ名のみがシリアル化されました。

unserialize($sub_str)」の部分でシリアル化されたオブジェクトの非シリアル化を試みると、「__wakeup()」メソッドがコールされました。今回のサンプルでは特に何の役にも立っていません。で、非シリアル化されオブジェクトが復元されました。

__sleep()」メソッドを持たないオブジェクトをシリアル化すると、全てのプロパティがシリアル化されました。また、「クラスプロパティ」はシリアル化されませんでした。

ついでに、「__wakeup()」メソッドが役に立つサンプルを以下に。

aaa
bbb
ccc
ddd

↑「serialize.log」という名前のログファイル

<pre>
<?php
    class HODE{
        private $filename = "./serialize.log";
        private $fp;
        private $fp_posi;
        public function __construct(){
            $this->fp = fopen($this->filename, "r");
        }
        public function __sleep(){
            echo "\n****** zzz __sleep() zzz ******\n\n";
            $this->fp_posi = ftell($this->fp);
            fclose($this->fp);
            return array_keys(get_object_vars($this));
        }
        public function __wakeup(){
            echo "\n******     __wakeup()    ******\n\n";
            $this->fp = fopen($this->filename, "r");
            fseek($this->fp, $this->fp_posi);
        }
        public function get_data(){
            return fgets($this->fp);
        }
    }
    
    $hode = new HODE;
    
    echo $hode->get_data();
    echo $hode->get_data();
    
    #面倒なので一行にしました(オイ)
    $hode = unserialize(serialize($hode));
    
    echo $hode->get_data();
    echo $hode->get_data();
?>
</pre>
aaa
bbb

****** zzz __sleep() zzz ******


******     __wakeup()    ******

ccc
ddd

ハイ~(エ○パー伊東)。

寝る時にファイルポインタ位置を記憶した後ファイルを閉じて、起きた時にファイルを開いて記憶した位置にファイルポインタを移動したってだけの簡単なサンプルです。実用性は別として、「__wakeup()」メソッドが役に立ちました。

クラスが未定義であった時にコールされる特殊関数

オブジェクトの「特殊メソッド」の他に、「__autoload()」関数という「特殊関数」もあります。

オブジェクトを生成する時やクラス関数を静的にコールする時などに、そのクラスが定義されていないと、当然「E_ERROR」エラーを発するわけですが、「__autoload()」関数を定義して置くと、エラーを発する前にこの関数がコールされます。この関数は引数にアクセスしようとした「クラス名」を受け取り、これを元にクラス定義ファイルをインクルードする事が出来ます。

__autoload()関数
void __autoload(string クラス名)
オブジェクトを生成しようと思ったら(もしくはクラス関数を静的にコールしようと思ったら)クラスが未定義であった時にコールされる関数。
通常、受け取った「クラス名」を元に、クラス定義ファイルのインクルードを試みる記述をします。インクルードするファイル名はクラス名から割り出せるようにして置く必要があります。通常はクラス名を含んだファイル名を付けます。

以下のサンプルと結果をご覧下さい。

まず、「hode.inc」という名前のファイルを作成して、次のように記述します。

<?php
    class HODE{
        public function __construct(){
            echo "I am HODENASU.";
        }
    }
?>

次に、別ファイルに下記の様に記述して実行します。

<pre>
<?php
    function __autoload($classname){
        echo "****** Begin __autoload() ******\n\n";
        $load_filename = "./" . strtolower($classname) . ".inc";
        echo "aotoloading... : ", $load_filename, "\n\n";
        @include_once $load_filename;
        if(!class_exists($classname)){
            /*
            エラー時の処理。
            */
            //die($classname." class not exists.");
        }
        echo "****** End __autoload() ******\n\n";
    }
    
    #インスタンスの生成を試みる
    new HODE;
    
    #未定義のクラスを継承するクラスの定義
    class NDA extends APETOPE{
        //
    }
?>
</pre>
****** Begin __autoload() ******

aotoloading... : ./hode.inc

****** End __autoload() ******

I am HODENASU.


****** Begin __autoload() ******

aotoloading... : ./apetope.inc

****** End __autoload() ******

Fatal error:「APETOPE」クラスが見当たりません。

new HODE;」の部分で「HODE」クラスのインスタンスの生成を試みますが、「HODE」クラスは未定義です。そこで「__autoload()」関数がコールされ、渡された「クラス名」を元に、クラス定義ファイルをインクルードした後、再度「new HODE;」の部分が評価されます。一方、「class NDA extends APETOPE{}」と未定義クラスの派生クラスを定義しようとした場合も同様に「__autoload()」関数がコールされましたが、コール後もなお「APETOPE」クラスが未定義です。この場合は再び「__autoload()」関数がコールされる事はなく、「E_ERROR」エラーを発します。

更にもう1サンプル。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    function __autoload($classname){
        echo "****** Begin __autoload() ******\n\n";
        echo "aotoloading... : 'class {$classname}'\n\n";
        eval( <<< EOT
    class {$classname}{
            private \$classname = "{$classname}";
            public function __call( \$string_name, \$array_arguments ){
                echo "'".\$this->classname."::".\$string_name."()' not exists.\n\n";
            }
    }

EOT
             );
        echo "****** End __autoload() ******\n\n";
    }
    
    #インスタンスの生成を試みる
    $hode = new HODE;
    
    #メソッドをコールしてみる
    $hode->method1();
    $hode->method2();
    $hode->method3();
    
    #ついでに違うクラスのインスタンス生成&メソッドコールを試みる
    $apetope = new APETOPE;
    $apetope->method();
    
    #更についでにもう1度インスタンス生成&メソッドコールしてみる
    $apetope = new APETOPE;
    $apetope->method();
?>
</pre>
****** Begin __autoload() ******

aotoloading... : 'class HODE'

****** End __autoload() ******

'HODE::method1()' not exists.

'HODE::method2()' not exists.

'HODE::method3()' not exists.

****** Begin __autoload() ******

aotoloading... : 'class APETOPE'

****** End __autoload() ******

'APETOPE::method()' not exists.

'APETOPE::method()' not exists.

未定義クラスのインスタンス生成を試みた場合にコールされる「__autoload()」関数内で、そのクラスを定義してしまっています。何だか怪しい感じですが、うまい事いきました。更に、定義するクラスに「特殊メソッド」の「__call()」メソッドを持たせ、メソッドがコールされた場合でも「E_ERROR」エラーが発せられないようにしています。「__autoload()」関数がコールされた時点でインスタンス生成を試みたクラスは定義されるので、再度同じクラスのインスタンス生成を試みた場合は「__autoload()」関数はコールされません。

__autoload()」関数内の「eval()」関数の引数にあえてヒアドキュメント構文を使っていますが、試しにやってみたら出来ちゃったってだけの事で、何だか怪しいので普通に「"(ダブルクォーテーション)」で括った方が良いかもしれません。

作成日:2004年12月24日 最終更新日:2004年12月24日
【印刷モード風モード で表示】