遅延静的束縛 (Late Static Bindings)

PHP 5.3.0 以降、PHP に遅延静的束縛と呼ばれる機能が搭載されます。 これを使用すると、静的継承のコンテキストで呼び出し元のクラスを参照できるようになります。

より正確に言うと、遅延静的束縛は直近の "非転送コール" のクラス名を保存します。 静的メソッドの場合、これは明示的に指定されたクラス (通常は :: 演算子の左側に書かれたもの) となります。静的メソッド以外の場合は、そのオブジェクトのクラスとなります。 "転送コール" とは、self::parent::static:: による静的なコール、 あるいはクラス階層の中での forward_static_call() によるコールのことです。 get_called_class() 関数を使うとコール元のクラス名を文字列で取得できます。 static:: はこのクラスのスコープとなります。

この "遅延静的束縛" という機能名は、内部動作を考慮してつけられたものです。 "遅延束縛 (Late binding)" の由来は、メソッドを定義しているクラス名を使用しても static:: の解決ができないことによります。 その代わりに、実行時情報をもとに解決するようになります。 "静的束縛 (static binding)" の由来は、 静的メソッドのコールに使用できることによります (ただし、静的メソッド以外でも使用可能です)。

self:: の制限

self:: あるいは __CLASS__ による現在のクラスへの静的参照は、 そのメソッドが属するクラス (つまり、 そのメソッドが定義されているクラス) に解決されます。

例1 self:: の使用例

<?php
class {
    public static function 
who() {
        echo 
__CLASS__;
    }
    public static function 
test() {
        
self::who();
    }
}

class 
extends {
    public static function 
who() {
        echo 
__CLASS__;
    }
}

B::test();
?>

上の例の出力は以下となります。

A

遅延静的束縛の使用法

遅延静的束縛は、この制限を解決するためのキーワードを導入し、 実行時に最初にコールされたクラスを参照するようにしています。 このキーワードを使用すると、先ほどの例における test() から B を参照できるようになります。 このキーワードは新たに追加したものではなく、すでに予約済みである static を使用しています。

例2 static:: のシンプルな使用法

<?php
class {
    public static function 
who() {
        echo 
__CLASS__;
    }
    public static function 
test() {
        static::
who(); // これで、遅延静的束縛が行われます
    
}
}

class 
extends {
    public static function 
who() {
        echo 
__CLASS__;
    }
}

B::test();
?>

上の例の出力は以下となります。

B

注意:

静的でないコンテキストでは、呼び出されるクラスはそのオブジェクトのクラスと同じになります。 $this-> は private メソッドを同じスコープからコールしようとするので、 static:: を使うと異なる結果となります。もうひとつの相違点は、 static:: は静的なプロパティしか参照できないということです。

例3 非静的コンテキストにおける static:: の使用法

<?php
class {
    private function 
foo() {
        echo 
"success!\n";
    }
    public function 
test() {
        
$this->foo();
        static::
foo();
    }
}

class 
extends {
   
/* foo() が B にコピーされるので、メソッドのスコープは A のままとなり、
    * コールは成功します */
}

class 
extends {
    private function 
foo() {
        
/* もとのメソッドが置き換えられるので、新しいメソッドのスコープは C となります */
    
}
}

$b = new B();
$b->test();
$c = new C();
$c->test();   //fails
?>

上の例の出力は以下となります。

success!
success!
success!


Fatal error:  Call to private method C::foo() from context 'A' in /tmp/test.php on line 9

注意:

遅延静的束縛の解決は、静的コールが代替なしに完全に解決された時点で終了します。 一方、parent::self:: といったキーワードを使用する静的コールは、コール元の情報を転送します。

例4 転送するコールと転送しないコール

<?php
class {
    public static function 
foo() {
        static::
who();
    }

    public static function 
who() {
        echo 
__CLASS__."\n";
    }
}

class 
extends {
    public static function 
test() {
        
A::foo();
        
parent::foo();
        
self::foo();
    }

    public static function 
who() {
        echo 
__CLASS__."\n";
    }
}
class 
extends {
    public static function 
who() {
        echo 
__CLASS__."\n";
    }
}

C::test();
?>

上の例の出力は以下となります。

A
C
C