1. 程式人生 > >PHP 中 parent、self、static、$this 的區別 & 後期靜態繫結詳解

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 的指向,會一直向上追溯,直到遇到非轉