Self, this, static 在 PHP 中的差異

前言

俗話說的好, 今天你不弄死他, 改天他就弄死你。 同理可證, 今天你不搞懂它, 改天你照樣會在讀自己或別人寫的 code 時被弄死。
就讓我們一起一次搞懂它吧!

Static Variable

曾幾何時, 我也幻想, 如果可以不看 example code 就可以理解 OOP, 那該是多好的一件事啊! 如果你也跟我有同樣的想法, 那… 醒醒吧, 別作夢了! 蛤? 你說你很清醒? 那趕快洗洗睡吧, 這裡沒你什麼事了…
一般來說, 變數在 function 執行完畢時就會釋放, 如下面的 example:

<?php
function test()
{
$var = 0;
$var += 1;
echo $var . PHP_EOL;
}


function testtest()
{
$var = 0;
$var += 1;
echo $var . PHP_EOL;
}

test(); // 1
test(); // 1
test(); // 1
testtest(); // 1
testtest(); // 1
testtest(); // 1
?>

然而, 我們只需在變數前面加上 static, 它就會變成 static variable, static variable 會將值保留下來, 如下 example, 不同的 function 中的 static variable 會分別各自累加上一次的結果

  • Example:
    <?php
    function test()
    {
    static $var;
    $var += 1;
    echo $var . PHP_EOL;
    }


    function testtest()
    {
    static $var;
    $var += 1;
    echo $var . PHP_EOL;
    }

    test(); // 1
    test(); // 2
    test(); // 3
    testtest(); // 1
    testtest(); // 2
    testtest(); // 3
    ?>

從上面的 example 可以看到, 不同 function 中定義的 static variable 是不會互相影響的, 但若是定義為 class property, 那作用範圍便是套用到全部的 method, 如下 example, 不同 method 的執行結果會累加到 static variable

  • Example:
    <?php
    class A
    {
    static $var;

    public static function test()
    {
    self::$var += 1;
    echo self::$var . PHP_EOL;
    }


    public static function testtest()
    {
    self::$var += 1;
    echo self::$var . PHP_EOL;
    }

    }

    A::test(); // 1
    A::test(); // 2
    A::test(); // 3
    A::testtest(); // 4
    A::testtest(); // 5
    A::testtest(); // 6

    ?>




Static, this, self

下面的 example 有點大包, 其實我原本參照的資料來源更大包。 但其實只是字多了一點, 沒有複雜的 method 傳遞, 並且註解有詳述原理, 不經一番寒徹骨, 焉得梅花撲鼻香啊!

  • Example:
    <?php
    class A {

    protected $name = 'A';
    static $alias = 'a';
    const HASH = 'md5';

    public function dd() {
    echo $this->name; echo '--';
    echo static::$alias; echo '--';
    echo static::HASH; echo '--';
    echo self::$alias; echo '--';
    echo self::HASH; echo '--';

    var_dump(new self); echo '--';
    var_dump($this); echo '--';
    var_dump(new static); echo '<br>';
    }
    }

    class B extends A {
    protected $name = 'B';
    static $alias = 'b';
    const HASH = 'sha1';
    }

    // $this->name = A, $this 代表當前 instance, 而當前 instance 為
    // class A 的 instance, 故為 class A 中的 protected variable

    // static::$alias = a, static 為 forwarding call, static 的 forwarding call
    // 下面會詳述。 呼叫的 instance 的 class 為 class A, 所以取 class A 中定義的 static variable

    // static::HASH = md5, 原理同上

    // self::$alias = a, self 表示當前 method 歸屬的 class, 此處為 class A,
    // 因此使用 class A 中定義的 static variable

    // self::HASH = md5, 原理同上

    // new self = class A, 如同上面提到的, 當前 method 所在的 class 為 class A

    // $this = class A, 原理同上, $this 為 class A 的 instance, 所以是 class A

    // new static = 同上, 原始被呼叫的 class 為 class A, 所以為 class A
    (new A)->dd();
    // 輸出為: A--a--md5--a--md5--object(A)#2 (1) { ["name":protected]=> string(1) "A" }
    // --object(A)#1 (1) { ["name":protected]=> string(1) "A" }
    // --object(A)#2 (1) { ["name":protected]=> string(1) "A" }

    // $this->name = B, $this 代表當前 instance, 也就是 class B

    // static::$alias = b, static 為 forwarding call, static 的 forwarding call 下面會詳述。
    // 呼叫的 instance 的 class 為 class B, 所以取 class B 中定義的 static variable

    // static::HASH = sha1, 原理同上

    // self::$alias = a, self 表示當前 method 歸屬的 class, 因 class B 使用的 test() method 是
    // 繼承自 class A 的, 實際上 self 為 class A, 所以使用定義在 class A 的 alias

    // self::HASH = md5, 原理同上

    // new self = class A, 如同上面提到的, 當前 method 定義於 class A

    // $this = class B, 原理同上, $this 為 class B 的 instance, 所以是 class B

    // new static = 同上, 原始被呼叫的 class 為 class B, 所以為 class B

    (new B)->dd();
    // 輸出為: B--b--sha1--a--md5--object(A)#2 (1) { ["name":protected]=> string(1) "A" }
    // --object(B)#1 (1) { ["name":protected]=> string(1) "B" }
    // --object(B)#2 (1) { ["name":protected]=> string(1) "B" }


self

首先, 先來看看 self 吧! self 代表著當下的 class, 跟 __class__ 是一樣的。 啥? 看無? 都說了, 要看 example code 嘛!

  • Example:
    <?php
    class A {
    public static function who() {
    echo __CLASS__;
    }
    public static function test() {
    self::who();
    }
    }

    class B extends A {
    public static function who() {
    echo __CLASS__;
    }
    }

    B::test();
    // 輸出為 A
    // 因為 B 本身沒有 test(), 所以調用 A 的 test(), 而 test() 中的
    // self 代表 test() 歸屬的 class, 即 A, 所以會調用 A 的 who()
    ?>


static

上面看完 self, 接下來瞧瞧 static, static:: 代表上一個調用 non-forwarding call 的 class, 不懂沒關係, 這個特性叫做 Late Static Bindings, 下面會詳述。 一樣看一段 example

  • Example:
    <?php
    class A {
    public static function who() {
    echo __CLASS__;
    }
    public static function test() {
    // 唯一跟上一個 example 不同之處, 把 self 換成了 static
    static::who();
    }
    }

    class B extends A {
    public static function who() {
    echo __CLASS__;
    }
    }

    B::test();
    // 輸出為 B
    // static 代表著上一個調用 non-forwarding call 的 class, 即 B
    // 下面會針對 forwarding call 以及 non-forwarding call 詳述
    ?>


Late Static Bindings

這幾個字看似玄乎, 可其實就嚇嚇沒耐心的人而已! static method 都會有 Late Static Bindings 的特性, 而這個特性是什麼呢?
static method 會把最後一次調用 non-forwarding call 的 class name 記錄下來, 也就是說, static 代表的 class 就是上一次呼叫 non-forwarding call 的 class
那什麼是 forwarding call 呢? 下面幾種都算:

  • self::
  • static::
  • parent::
  • forward_static_call()

上面這些都是 forwarding call, 那 non-forwarding call 呢? 比如 A::test(), 這就是 non-forwarding call, 而 A 就是呼叫 non-forwarding call 的 class
看不懂對吧? 哈哈, 你一撅屁股我就知道你要拉什麼屎! 這樣你就看得懂那你就是天才了, 我自己寫的人都看不太懂了。
所以我開頭說了嘛, 想不讀 example code 就想了解 OOP, 根本是癡人說夢啊!
還是乖乖認命地在看幾段 example code 吧! 看完 example code 之後再上來看這一段, 就懂了!

Example:

<?php
class A {
public static function foo() {
// who() 為 echo called class name, 因此 static 代表什麼, 決定著輸出
static::who();
}

public static function who() {
echo __CLASS__."\n";
}
}

class B extends A {
public static function test() {
// 上面有提到 A:: 算 non-forwarding call, 所以假如後面有呼叫 static:: 的話, static 就會代表 A
A::foo();
// 上面有提到, parent:: 算 forwarding call, 而上一個 non-forwarding call
// 為 C::test(), 因此如果後面有使用 static::, static 表示 C
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
// A::foo() 輸出 A, 如上所述, A:: 為 non-forwarding call,
// 因此 static::who() 這邊的 static 代表 A

// parent::foo() 輸出 C, 如上所述, parent:: 為 forwarding call,
// 因此 foo() 中的 static 還是代表上一個 non-forwarding call, 即 C

// self::foo() 輸出為 C, 同上
?>

看完上面的 example, 是不是比較看得懂 Late Static Bindings 特性以及 forwarding call 跟 non-forwarding call 的差異了呢? 下面繼續聊聊 self 跟 static 的差異


self 與 static

self 與 static 都可以做到以下的事

  • 取用 static property
  • 取用 constant
  • 取用 static method

差別處在於

  • self 代表當前 method 所歸屬的 class
  • static 代表上一個 non-forwarding 調用的 class

這樣是不是清楚又明瞭呢?
接下來, 我們來看看在 non-static 的狀況下, 又會有什麼特別的行為


$this 與 static

在進到 non-static 環境之前, 先來看看 $this 跟 static 有何差異
相同處:

  • 原則上, 兩者指向的 class 都相同, 除非調用 static 之前有呼叫 non-forwarding call 而變更了 static 的指向, 這個上面有詳述, 還有點模糊的話再上去看一次。

不同處:

  • $this 不可調用 static property, constant, 以及 static method; 反之, static 也不可調用 non-static property
  • $this 指向的是實際調用的 instance, 而 static 指向的是調用的 class, 原則上兩者會是同一個 class, 但不同的 object

好啦, 接下來就來看看在 non-static context 中, $this 與 static 不同的行為


non-static context (非靜態環境)

首先, 先看看以下兩點 $this 與 static 不同的行為, 再對照下面的 example code 就會更明白

  • static 會先到 它指向的 class 中找尋 private method, 在尋找 public method, 如果沒找到, 再去 調用 static 的 method 所在的 class 中尋找 private method, 然後是 public method, 如果存在就調用, 並停止找尋。
  • $this 會先到 調用 $this 的 method 所在的 class 中尋找 private method, 再到 它指向的 instance 的 class 中尋找 private method, 然後是 public method, 再來是在 調用 $this 的 method 所在的 class 中尋找 public method, 只要找到就調用, 並停止找尋

  • Example

    <?php
    class A {
    private function foo() {
    echo __class__ . PHP_EOL;
    }
    public function test() {
    $this->foo();
    static::foo();
    }
    }

    class B extends A {
    // foo() 是從 A copy 過來 B 的, 因此它的 scope 依然是 A
    }

    class C extends A {
    private function foo() {
    // 這個 foo() 覆蓋了 class A 的 foo(), 所以新的 scope 為 C
    }
    }

    $b = new B();

    // $this->foo() 輸出為 A, 如上所述, class B 本身沒有 test(), 所以會調用 class A 的
    // 而 $this 會先到 "調用 $this->foo()" 的 method, 即 test(), 所在的 class, 即 class A,
    // 中尋找 private method, 你可以試試在 class B 中新增一個一模一樣的 private method foo(),
    // 結果還是會先調用 class A 的 foo()

    // static::foo() 輸出為 A, 同上, 但 class B 並沒有 foo(), 所以調用了 class A 的 foo()
    // 可以試試在 class B 新增一個 private method foo(), 這樣會報錯, 因為 static 會先從實際調用
    // class, 即 class B, 尋找 private method, 這樣就變成從 class A 去呼叫 class B 的 private
    // method, private method 只可從所屬的 class 中才可調用, 當然, 如果改成 public method,
    // 那輸出就會變成 B
    $b->test();

    $c = new C();

    // $this->foo() 輸出是 A, 這點同上, 不加贅述
    // static::foo() 會 fail, 因為 static 會到實際調用的 class 中尋找 private method, 而
    // class C 中確實定義了 private method foo(), 這樣就變成了從 class A 的 scope 去調用
    // class C 的 private method, 所以錯了
    // 可以試試把 class C 中的 foo() 改成 public, 且輸入跟 class A foo() 一樣的內容
    // 這樣輸出就會變成 C 了
    $c->test();
    ?>


結語

寫這篇寫了兩天, 這兩天只有上帝跟我知道我在寫什麼, 所以我盡可能地把它寫的詳盡, 一方面也是取之於網路, 回饋於網路。
另一方面是, 我不想幾個禮拜之後只剩上帝知道這篇在寫什麼。 搞不好祂也不知道嗚嗚嗚。
若內容有錯誤, 請不吝指教, 我當立即修正以避免誤導大家。

歡迎轉載, 但請註明出處!




參考來源

PHP-Late Static Bindings
CSDN lamp_yang_3533 的部落格
程式狂想筆記

Laravel - Documentation - 目錄 (官方文件原子化翻譯) Laravel - Eloquent ORM - Getting Started

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×