對 PHP 後期靜態繫結的理解

JeffreyC發表於2018-03-21

什麼是後期靜態繫結

在看一些框架原始碼或者是某個專案的程式碼時,經常能看到後期靜態繫結的用法
。比如下面這段:

public static function getInstance()
{
    if (is_null(static::$instance)) {
        static::$instance = new static;
    }
    return static::$instance;
}

這裡用到的就是後期靜態繫結。那麼,什麼是後期靜態繫結?

“後期繫結”的意思是說,static:: 不再被解析為定義當前方法所在的類,而是在實際執行時計算的。

這裡要先說兩個概念,一個是轉發呼叫,另一個是非轉發呼叫。

  • 轉發呼叫
    所謂的“轉發呼叫”(forwarding call)指的是通過以下幾種方式進行的靜態呼叫:self::, parent::, static:: 以及 forward_ static _call()。即在進行靜態呼叫時未指名類名的呼叫屬於轉發呼叫。

  • 非轉發呼叫
    非轉發呼叫其實就是明確指定類名的靜態呼叫(foo::bar())和非靜態呼叫($foo->bar())。即明確地指定類名的靜態呼叫和非靜態呼叫。

顧名思義,非轉發呼叫前面有類名所以呼叫的函式一定是屬於“這個類的”,不需要轉到別的類。轉發呼叫就是由於前期的靜態繫結導致在後面呼叫靜態方法時可能“轉發到其他的類”

在PHP的官方文件裡,對於後期靜態繫結是這樣說的:後期靜態繫結工作原理是儲存了在上一個“非轉發呼叫”(non-forwarding call)中的類名。意思是當我們呼叫一個轉發呼叫的靜態呼叫時,實際呼叫的類是上一個非轉發呼叫的類。

來看兩個例子:

例1:

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

例2:

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

在這裡主要分析下例2。

1.C::test(),這是一個非轉發呼叫,因為::前面有類名C。

2.進入test()方法,有三個靜態呼叫 A::foo(),parent::foo(),self::foo(),對於這三個靜態呼叫來說,他們的非轉發呼叫類就是 C。

3.現在執行A::foo(),這是一個非轉發呼叫。A::foo()中的程式碼是 static::who(),這是一個轉發呼叫,對於這個轉發呼叫來說他的非轉發呼叫類就是不再是C而是A(因為之前執行了A::foo())。因此執行的結果為A

4.現在執行 parent::foo(),這是一個轉發呼叫,轉發到哪裡呢?就是它的上一個非轉發呼叫的類,也就是類C(在步驟2中提到的)。在這裡一定要注意雖然在這之前執行了 A::foo(),但是parent::foo()的上一個非轉發呼叫的類任然是類C。因此執行的結果是 C.

5.現在執行 self::foo(),這個和 parent::foo()一樣都是轉發呼叫,因此也輸出 C。

使用後期靜態繫結的好處

後期靜態繫結目前我看到較多的是用於物件例項化中,在例項化物件時,static 會根據執行時呼叫的類來決定例項化物件,而 self 則是根據所在位置的類來決定例項化物件。當我們只想例項化子類,並且不希望後續在對子類的使用中由於父類的變化對子類產生影響時,後期靜態繫結就能發揮它的作用了。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

You can not connect the dots looking forward, you can only connect them looking backwards.

相關文章