PHP 中 parent、self、static、$this 的區別 & 後期靜態繫結詳解
自 PHP 5.3.0 起,PHP 增加了一個叫做後期靜態繫結的功能,用於在繼承範圍內引用靜態呼叫的類。 雖然也可以呼叫非靜態方法,但是不會在執行時繫結。
static 不再只是簡單的靜態修飾關鍵字。而是還可以呼叫類的靜態方法,非靜態方法,為什麼靜態非靜態要分開說呢,因為呼叫的效果是不一樣的。
例項
class A { protected $name = 'A'; static $alias = 'a'; const HASH = 'md5'; public function dd() { echo 'name:' . $this->name . PHP_EOL; echo 'self-alias:' . self::$alias . PHP_EOL; echo 'static-alias:' . static::$alias . PHP_EOL; // 後期靜態繫結 echo 'self-hash:' . self::HASH . PHP_EOL; echo 'static-hash:' . static::HASH . PHP_EOL; // 後期靜態繫結 var_dump(new self); var_dump($this); var_dump(new static); } public static function who () { echo __CLASS__ ; echo ' [ This is A ]'; echo PHP_EOL; } public static function test () { static:: who (); // 後期靜態繫結從這裡開始 } public static function test2 () { self:: who (); } } class B extends A { protected $name = 'B'; static $alias = 'b'; const HASH = 'sha1'; public static function who () { echo __CLASS__ ; echo ' [ This is B ]'; echo PHP_EOL; } } (new B)->dd()
結果輸出:
name:B self-alias:a static-alias:b self-hash:md5 static-hash:sha1 object(admin\controllers\A) protected 'name' => string 'A' (length=1) object(admin\controllers\B) protected 'name' => string 'B' (length=1) object(admin\controllers\B) protected 'name' => string 'B' (length=1)
執行:
B::who();
B::test();
B::test2();
輸出:
B [ This is B ]
B [ This is B ]
A [ This is A ]
總結說明:
-
self
和__CLASS__
,都是對當前類的靜態引用
,取決於定義當前方法所在的類。也就是說,self 寫在哪個類裡面, 它引用的就是誰。 -
$this
指向的是實際呼叫時的物件,也就是說,實際執行過程中,誰呼叫了類的屬性或方法,$this
指向的就是哪個物件。但$this
不能訪問類的靜態屬性和常量,且$this
不能存在於靜態方法中。 -
static
關鍵字除了可以宣告類的靜態成員(屬性和方法)外,還有一個非常重要的作用就是後期靜態繫結。 -
parent
,是對當前類的父類的靜態引用。 -
self
可以用於訪問類的靜態屬性、靜態方法和常量,但self
指向的是當前定義所在的類,這是self
的限制。 -
$this
指向的物件所屬的類和static
指向的類相同。 -
static
可以用於靜態或非靜態方法中,也可以訪問類的靜態屬性、靜態方法、常量和非靜態方法,但不能訪問非靜態屬性。 -
靜態呼叫時,
static
指向的是實際呼叫時的類;非靜態呼叫時,static
指向的是實際呼叫時的物件所屬的類。
後期靜態繫結
後期靜態繫結(也叫延遲靜態繫結),可用於在繼承範圍內引用靜態呼叫的類,也就是程式碼執行時最初呼叫的類。
工作原理
確切地說,static 後期靜態繫結的工作原理是儲存了上一個非轉發呼叫(non-forwarding call)的類名。
當進行靜態方法呼叫時,該類名(static指向的類名)為明確指定的那個(通常是 :: 運算子的左側部分),即實際呼叫時的類。
如:上面例子中的
A::test(); //A::test() 呼叫的是 static::who(),這裡static指向的便是A,所以執行的就是A::who();
B::test(); //A::test() 呼叫的是 static::who(),這裡static指向的便是B,所以執行的就是B::who();
static指向的類名,指向的就是實際呼叫的類
對比
static 和 self 的區別:
-
self 可以用於訪問類的靜態屬性、靜態方法和常量,但 self 指向的是當前定義所在的類,這是 self 的限制。
-
static 也可以用於訪問類的靜態屬性、靜態方法和常量,static 指向的是實際呼叫時的類。當進行非靜態方法呼叫時,該類名(static指向的類名)為該物件所屬的類,即實際呼叫時的物件所屬的類。
static 和 $this 有點類似,但又有區別:
- $this 指向的物件所屬的類和 static 指向的類相同。
- $this 不能用於靜態方法中,也不能訪問類的靜態屬性和常量。
- $this 指向的是實際呼叫的物件。
- static 可以用於靜態或非靜態方法中,也可以訪問類的靜態屬性、靜態方法、常量和非靜態方法,但不能訪問非靜態屬性。
- static 指向的是實際呼叫時的物件所屬的類。
轉發呼叫(forwarding call)
所謂的轉發呼叫(forwarding call)指的是通過以下幾種方式進行的靜態呼叫:self::,parent::,static:: 以及 forward_static_call() 。
可用 get_called_class() 函式來獲取被呼叫的方法所在的類名。
以下四種形式的呼叫,都是轉發呼叫:
self::
parent::
static::
forward_static_call()
除此之外的呼叫,就是非轉發呼叫。
非轉發呼叫(non-forwarding call)
後期靜態繫結的工作原理是儲存了上一個非轉發呼叫(non-forwarding call)的類名。
通過具體的類名或具體的物件進行的呼叫都是非轉發呼叫。
注意事項
非靜態環境下的私有方法的查詢順序
在非靜態環境下,在類的非靜態方法中,使用 $this 和 static 呼叫類的私有方法時,執行方式有所不同。
- $this 會優先尋找所在定義範圍(父類)中的私有方法,如果存在就呼叫。
- static 是先到它指向的類(子類)中尋找私有方法,如果找到了就會報錯,因為私有方法只能在它所定義的類內部呼叫;如果沒找到,再去所在定義範圍(父類)中尋找該私有方法,如果存在就呼叫。
具體來說,$this 會先到所在定義範圍內尋找私有方法,再到它指向的物件所屬的類中尋找私有方法,然後尋找公有方法,最後到所在定義範圍內尋找公共方法。只要找到了匹配的方法,就呼叫,並停止查詢。
而 static 則是先到它指向的類中尋找私有方法,再尋找共有方法;然後到所在定義範圍內尋找私有方法,再尋找共有方法。只要找到了匹配的方法,就呼叫,並停止查詢。
下面是一個例子:
<?php
class A {
private function foo () {
var_dump($this); echo '--';
var_dump(new static); echo '--';
echo __CLASS__; echo '--';
echo get_called_class();
echo '<br>';
}
public function test () {
$this -> foo ();
static:: foo ();
echo '<br>';
}
}
class B extends A { }
class C extends A {
private function foo () {
echo 'this is C';
}
}
(new B())->test();
(new C())->test();
輸出結果為:
object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
object(C)#1 (0) { } --object(C)#2 (0) { } --A--C
Fatal error: Uncaught Error: Call to private method C::foo() from context 'A'
關於後期靜態繫結的解析
後期靜態繫結的解析會一直到取得一個完全解析了的靜態呼叫為止。如果靜態呼叫使用了 parent:: 或者 self:: 等轉發呼叫的形式,將會轉發呼叫資訊。
<?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 ();
static::foo();
forward_static_call(['A', 'foo']);
echo '<br>';
}
public static function who () {
echo __CLASS__ . "\n" ;
}
}
class C extends B {
public static function who () {
echo __CLASS__ . "\n" ;
}
public static function test2() {
self::test();
}
}
class D extends C {
public static function who () {
echo __CLASS__ . "\n" ;
}
}
B::foo();
B::test();
C::foo();
C::test();
D::foo();
D::test2();
以上的輸出結果為:
B A B B B B
C A C C C C
D A D D D D
static 後期靜態繫結的工作原理是儲存了上一個非轉發呼叫(non-forwarding call)的類名。請記住這句話。
下面的例子是非轉發呼叫。
A::foo(); // 輸出 A
B::foo(); // 輸出 B
C::foo(); // 輸出 C
後期靜態繫結 static ,是定義在了 foo() 方法中,哪個類通過非轉發呼叫的形式呼叫 foo() 方法, foo() 方法中的 static 指向的就是哪個類。
但是,如果通過轉發呼叫的形式,呼叫 foo() 方法,如:
parent :: foo ();
self :: foo ();
static::foo();
forward_static_call(['A', 'foo']);
那麼,就以轉發呼叫程式碼所在的方法 test() 為準,哪個類通過非轉發呼叫的形式呼叫 test() 方法, foo() 方法中的 static 指向的就是哪個類。
假如呼叫 test() 方法時,也採用了轉發呼叫的形式,如:
public static function test2() {
self::test();
}
那麼,就以 test2() 方法為準 ... 依次類推。
也就是說,在使用了後期靜態繫結的基類中,後期靜態繫結所在的方法如果被轉發呼叫,則 static 的指向,會一直向上追溯,直到遇到非轉