2022年1月2日

寫給 PHP 新手的物件導向教學(四):this,self 與 static

上一回《寫給 PHP 新手的物件導向教學(三):封裝,可見度與繼承》

在前面幾篇文章中,有稍微帶過 $this 的用法,因為只是簡單帶過,所以沒有詳細說明,因此在這一回,我們會討論更多關於 $this 的部分,另外本回也會討論另一個容易跟 $this 混淆的 selfstatic 的用法。

$this

$this 是 PHP 預先定義好的變數,在物件內部被用來表示自身。

class Student 
{
    public $firstName;
    public $lastName;
    
    public function __construct($firstName, $lastName) 
    {
		$this->firstName = $firstName;
		$this->lastName = $lastName;
    }
    
    public function getFirstName()
    {
        return $this->firstName;
    }
    
    public function getLastName()
    {
        return $this->lastName;
    }
    
    public function getFullName()
    {
        return $this->getFirstName() . ' ' . $this->getLastName();
    }
}

需要特別留意的地方在於,由於每個物件都是獨立的,所以每個物件中的 $this 都分別代表自己。 例如下面的例子中,我們有兩個 Student 物件,它們透過 getFirstName() 方法,分別取出了自己的 firstName

$student1 = new Student('Jack', 'Wong');
echo $student1->getFirstName(); // 'Jack'

$student2 = new Student('Tom', 'Lee');
echo $student2->getFirstName(); // 'Tom'

當然,透過 $this 除了可以在類別內部讀取成員變數以外,也可以操作成員方法。在下面的例子中,呼叫了 getFullName 方法,並且在其中透過 $this 呼叫了成兩個成員方法,組成 full name 後回傳。

$student1 = new Student('Jack', 'Wong');
echo $student1->getFullName(); // 'Jack Wong'

因為 $this 只有在物件中才有意義,所以在物件外是無法使用 $this

self 與 static

我們曾在第一回提到類別與物件的不同,當時使用模具的概念來譬喻。我們現在也可以換另一個方式來看,當一群物件,都是由同一個 A 類別實體化(透過 new )而來的,那我們就可以稱這群物件都屬於 A 類別。

static 靜態的屬性或方法,在概念上來說,可以說是隸屬於類別的,因為我們並不需要實體化出一個物件,就可以直接透過類別來操作靜態成員,而這樣的靜態成員,在所有相同類別的物件中都是共享的。

相較於靜態成員,之前我們熟悉的,比較一般的用法,可以稱為 non-static 非靜態成員。

self

要操作靜態屬性及方法需要透過 PHP 保留字 self,並搭配::運算子 (Scope Resolution Operator,作用域解析運算子)。

在類別內使用的 self,它代表的是目前的類別,有一種 $this 的感覺,只是其替代的不是物件而是類別。

靜態屬性

要設置靜態屬性,只要在屬性的宣告前方,加上 static 關鍵字即可。

在下面的範例中,我們新增了一個靜態屬性 (static property) $staticCount 。並在建構子中對其進行運算,每當創建新的 Student 物件時,在建構子中就會對 $staticCount 加一。

另外也放置一個非靜態的 $count,待會可以來比較結果的不同。

class Student 
{
    public $firstName;
    public $lastName;
    public $count = 0;
    public static $staticCount = 0;
    
    public function __construct($firstName, $lastName) 
    {
      $this->firstName = $firstName;
      $this->lastName = $lastName;
        
      $this->count++; // 非靜態
      self::$staticCount++; // 靜態
    }
}

我們可以接著從下面的範例來體會「靜態成員屬於類別」是什麼意思。

根據我們之前對屬性的了解, $student1$student2$count 是互相獨立的,每次新的物件被實體化後,$count 就會從 0 開始,所以分別輸出結果都會是 1。

但是靜態屬性的 $staticCount 並不會重新計算,而是會壘加,因此它會隨著 Student 物件的實體化,不斷的累加。所以可以理解成,$staticCount 的值,是紀錄在 Student 類別中,而不是個別的 Student 物件。

在類別外部要讀取靜態成員也是透過 ::

echo Student::$staticCount; // 0

$student1 = new Student('Jack', 'Wong');
echo $student1->count; // 1
echo Student::$staticCount; // 1

$student2 = new Student('Tom', 'Lee');
echo $student2->count; // 1
echo Student::$staticCount; // 2

除了透過類別來取出靜態成員,也可以透過物件來取出,結果都會是一樣的。下面範例中,特別透過 $student2 來取出 $staticCount ,應該可以更清楚的看出靜態屬性在相同類別下的不同物件中是共享的。

$student3 = new Student('Mark', 'Chen');
echo Student::$staticCount; // 3
echo $student3::$staticCount; // 3
echo $student2::$staticCount; // 3

靜態方法

除了靜態屬性,方法也可以是靜態的。一樣只需要在方法定義中加入 static 關鍵字即可。

在下面的範例中,我們稍微修改了 Student 類別,新增了一個靜態方法 getStaticCount ,並在其中回傳靜態屬性 $staticCount

class Student {
    public $firstName;
    public $lastName;
    public static $staticCount = 0;
    
    public function __construct($firstName, $lastName) 
    {
      $this->firstName = $firstName;
      $this->lastName = $lastName;
        
      self::$staticCount++;
    }
    
    static function getStaticCount()
    {
        return self::$staticCount;
    }
}

一樣是透過 :: 方法來使用靜態方法。

echo Student::getStaticCount(); // 0
$student1 = new Student('Jack', 'Wong');
echo Student::getStaticCount(); // 1

不能在靜態方法中使用 $this

根據前面提到的,由於靜態方法不屬於物件,所以在靜態方法中使用 $this 是沒有意義,並且也會跳出警告的。

class Student 
{
  public $firstName;
  public $lastName;
  
  public function __construct($firstName, $lastName) 
  {
    $this->firstName = $firstName;
    $this->lastName = $lastName;
  }
  
  static function getFirstName()
  {
      return $this->firstName;
  }
}

例如,將 getFirstName 宣告為 static,並在其中使用 $this ,會跳出錯誤。

echo Student::getFirstName();

// PHP Fatal error:  
// Uncaught Error: Using $this when not in object context

小結

這一回除了解釋了 $this 的意思,也討論到重要的 static 概念和 self 的用法。 其實還有另外一個跟 staticself 有關的議題是 Late Static Bindings,但我個人認為這個議題比較進階一點,我們會晚點再討論。

重點回顧:

  1. static 靜態成員,概念上隸屬於類別;non-static 非靜態成員,概念上隸屬於物件。
  2. self 代替目前類別;$this 代表目前物件。可以透過 self 操作靜態成員;透過 $this 操作非靜態成員。
  3. 無須實體化類別,就可以使用靜態成員。
  4. 要操作靜態成員,需要透過 Scope Resolution Operator ::

目錄:寫給 PHP 新手的物件導向教學

下一回:寫給 PHP 新手的物件導向教學(五):const