前言
俗話說的好, 今天你不弄死他, 改天他就弄死你。 同理可證, 今天你不搞懂它, 改天你照樣會在讀自己或別人寫的 code 時被弄死。
就讓我們一起一次搞懂它吧!
Static Variable
曾幾何時, 我也幻想, 如果可以不看 example code 就可以理解 OOP, 那該是多好的一件事啊! 如果你也跟我有同樣的想法, 那… 醒醒吧, 別作夢了! 蛤? 你說你很清醒? 那趕快洗洗睡吧, 這裡沒你什麼事了…
一般來說, 變數在 function 執行完畢時就會釋放, 如下面的 example:
<?php |
然而, 我們只需在變數前面加上 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 |
看完上面的 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();
?>
結語
寫這篇寫了兩天, 這兩天只有上帝跟我知道我在寫什麼, 所以我盡可能地把它寫的詳盡, 一方面也是取之於網路, 回饋於網路。
另一方面是, 我不想幾個禮拜之後只剩上帝知道這篇在寫什麼。 搞不好祂也不知道嗚嗚嗚。
若內容有錯誤, 請不吝指教, 我當立即修正以避免誤導大家。
歡迎轉載, 但請註明出處!
留言