php 後期靜態繫結

3333333_ 發表於 2020-11-21
PHP
https://www.php.net/manual/zh/language.oop5.late-static-bindings.php

自 PHP 5.3.0 起,PHP 增加了一個叫做後期靜態繫結的功能,用於在繼承範圍內引用靜態呼叫的類。
準確說,後期靜態繫結工作原理是儲存了在上一個“非轉發呼叫”(non-forwarding call)的類名。當進行靜態方法呼叫時,該類名即為明確指定的那個(通常在 :: 運算子左側部分);當進行非靜態方法呼叫時,即為該物件所屬的類。所謂的“轉發呼叫”(forwarding call)指的是通過以下幾種方式進行的靜態呼叫:self::,parent::,static:: 以及 forward_static_call()。可用 get_called_class() 函式來得到被呼叫的方法所在的類名,static:: 則指出了其範圍。
該功能從語言內部角度考慮被命名為“後期靜態繫結”。“後期繫結”的意思是說,static:: 不再被解析為定義當前方法所在的類,而是在實際執行時計算的。也可以稱之為“靜態繫結”,因為它可以用於(但不限於)靜態方法的呼叫。

self:: 的限制
使用 self:: 或者 __CLASS__ 對當前類的靜態引用,取決於定義當前方法所在的類:
Example #1 self:: 用法
<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();
    }
}

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

B::test();
?>


以上例程會輸出:
A


後期靜態繫結的用法

後期靜態繫結本想通過引入一個新的關鍵字表示執行時最初呼叫的類來繞過限制。簡單地說,這個關鍵字能夠讓你在上述例子中呼叫 test() 時引用的類是 B 而不是 A。最終決定不引入新的關鍵字,而是使用已經預留的 static 關鍵字。

Example #2 static:: 簡單用法

<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // 後期靜態繫結從這裡開始
    }
}

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

B::test();
?>



以上例程會輸出:

B



Note:
在非靜態環境下,所呼叫的類即為該物件例項所屬的類。由於 $this-> 會在同一作用範圍內嘗試呼叫私有方法,而 static:: 則可能給出不同結果。另一個區別是 static:: 只能用於靜態屬性。

Example #3 非靜態環境下使用 static::

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

class B extends A {
   /* foo() will be copied to B, hence its scope will still be A and
    * the call be successful */
}

class C extends A {
    private function foo() {
        /* original method is replaced; the scope of the new one is 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


上面將 private 改成 public 就不會報錯
class C extends A {
    private function foo() {
        /* original method is replaced; the scope of the new one is C */
    }
}

改成
class C extends A {
    public function foo() {
        /* original method is replaced; the scope of the new one is C */
    }
}



Note:
後期靜態繫結的解析會一直到取得一個完全解析了的靜態呼叫為止。另一方面,如果靜態呼叫使用 parent:: 或者 self:: 將轉發呼叫資訊。
Example #4 轉發和非轉發呼叫

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

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

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

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

C::test();
?>


以上例程會輸出:

A
C
C