首先,我們透過一段程式碼來引入後期靜態綁定這個概念:
class A { public static function who() { echo __CLASS__, PHP_EOL; } public static function test() { self::who(); } } class B extends A { public static function who() { echo __CLASS__, PHP_EOL; } } B::test(); // A
在這段程式碼中,我們使用了self關鍵字,當使用B類別呼叫test()靜態方法時,self指向的是A類別的who()方法,因此,輸出的是A。別激動,這是普通的靜態綁定。 self關鍵字呼叫的內容取決於它定義時所在的類別。也就是說不管怎麼繼承,用哪個子類別來呼叫test()方法,self關鍵字都會呼叫的是A類別的who()方法。
而後期靜態綁定呢?其實有點像實例化的類別對象,每個實例化的對象,呼叫的都是自身,而不是父類別的屬性方法。普通的靜態呼叫可不是這樣,但是現實中我們又有這樣的需求,就像實例化物件的呼叫方式一樣來呼叫靜態屬性方法,這時,我們就可以使用static關鍵字來實現後期靜態綁定。
class C { public static function who() { echo __CLASS__, PHP_EOL; } public static function test() { static::who(); } } class D extends C { public static function who() { echo __CLASS__, PHP_EOL; } } D::test(); // D
當使用static關鍵字後,這裡D類別呼叫的test()方法內部呼叫的who()就是D類別自己了。
官方文件中的定義如下:
當進行靜態方法呼叫時,該類別名稱即為明確指定的那個(通常在:: 運算子左側部分);當進行非靜態方法呼叫時,即為該物件所屬的類別。
此功能從語言內部角度考慮被命名為「後期靜態綁定」。 「後期綁定」的意思是說,static:: 不再被解析為定義當前方法所在的類,而是在實際運行時計算的。也可以稱之為“靜態綁定”,因為它可以用於(但不限於)靜態方法的呼叫。
除了self和static關鍵字外,我們還有一個parent關鍵字,這個關鍵字的意義就很明顯了,呼叫父類別的靜態內容。我們同時用三個關鍵字一起來測試:
class E { public static function who() { echo __CLASS__, PHP_EOL; } public static function test() { self::who(); static::who(); } } class F extends E { public static function who() { echo __CLASS__, PHP_EOL; } } class G extends F { public static function who() { parent::who(); echo __CLASS__, PHP_EOL; } } G::test(); // E // F // G
最後,我們再來看兩個PHP的方法,一個是get_called_class()方法,用來取得目前呼叫的是哪個類別。在靜態方法中可以根據呼叫方式判斷目前類別是哪個類別來進行其他的業務邏輯操作。另一個是forward_static_call()方法,用於靜態方法的呼叫。
class H { public static function who() { echo __CLASS__ . ':' . join(',', func_get_args()), PHP_EOL; } public static function test() { echo get_called_class(), PHP_EOL; forward_static_call('who', 'a', 'b'); // xxx:a,b forward_static_call(['I', 'who'], 'c', 'd'); // I:c,d forward_static_call_array(['H', 'who'], ['e', 'f']); // H:e,f } } class I extends H { public static function who() { echo __CLASS__ . ':' . join(',', func_get_args()), PHP_EOL; } } function who() { echo 'xxx:' . join(',', func_get_args()), PHP_EOL; } H::test(); // H // xxx:a,b // I:c,d // H:e,f I::test(); // I // xxx:a,b // I:c,d // H:e,f
注意,如果forward_static_call()不指定類別名稱的話,將會呼叫全域的方法。 forward_static_call_array()則是將參數使用陣列傳遞。
测试代码: https://github.com/zhangyue0503/dev-blog/blob/master/php/202001/source/%E5%90%8E%E6%9C%9F%E9%9D%99%E6%80%81%E7%BB%91%E5%AE%9A%E5%9C%A8PHP%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8.php