在PHP語言中,變數都是儲存在雜湊表中,稱為變數符號表,其中變數名為雜湊表的鍵,變數名對應的容器zval的指標為雜湊表中的值。所有全域變數放在一張主符號表中(也就是數組$GLOBALS對應的雜湊表)。PHP語言有個特性,變數在命名時,$變數標識符後不能以數字開頭。例如我們在以下代碼:
<?php
$111= "my";
?>
會報如下錯誤:Parse error: syntax error, unexpected T_LNUMBER, expecting T_VARIABLE or '$' in...
從錯誤的描敘來看,這是一個語法錯誤,於是我們推論對變數名合法性的判斷應該是在編譯時間的文法分析階段。
為了證明觀點,我們可以試著在執行階段定義一個數字字元開頭的變數:
<?php
$a = 111;
$$a = "my"; //以變數$a的值作為變數名
echo $$a;
var_dump($GLOBALS);
?>
運行之後發現不報錯,並且在全域符號表$GLOBALS中發現相關符號:
["a"]=>
int(111)
["111"]=>
string(2) "my"
這樣我們就定義了一個全數字字元命名的變數,似乎違背了PHP的規則,但是確實做到了。
(讀者可以試著輸出$GLOBALS["111"]的值,雖然有值,但是結果卻為NULL,這個是PHP中一個類型轉換的特性)
在這段代碼中,$$a = "my"這條語句具有多態性,只有當實際執行到這條語句的時候,我們才能確定變數名,PHP在文法分析階段無法知道這個變數名會是什麼,所以就不會報錯,在執行階段,PHP語言不判斷變數名的合法性,於是就產生了這樣一個叛逆的變數。
知道了這個特性之後,馬上會想到一個另外一個特殊的變數:$this
在類的方法中,$this關鍵字用來指向當前類的對象執行個體,如果對$this進行賦值操作,會發生什麼事情?
<?php<br />class Person<br />{<br /> protected $_name = "phper";</p><p> protected $_age = 18;</p><p> public function getName()<br /> {<br /> $this = 123;<br /> return $this->_name;<br /> }</p><p> public function getAge()<br /> {<br /> return $this->_age;<br /> }<br />}</p><p>$p = new Person();<br />$p->getName();<br />?>
執行代碼,報錯:
Fatal error: Cannot re-assign $this in...
PHP對$this變數做了一定保護措施,但是這個保護措施也不是完全的,我們利用前面的方法在執行期改變$this的值, 修改後的getName方法為:
public function getName()<br /> {<br /> $a = "this";<br /> $$a = 123;<br /> echo $this;<br /> return $this->_name;<br /> }
然後執行代碼,發現能夠順利執行,$this的值變成了123, 由此可以判斷對$this關鍵字的保護也僅限與文法分析階段。
當$this的值變成123之後,按理說$this->_name肯定會報錯的,但是居然代碼能正常執行,這個確實很讓人覺得不可思議。於是推斷
$this->_name這樣的引用方式和一般的物件變數引用方式在有差異,我們繼續對getName做修改:
public function getName()<br /> {<br /> $a = "this";<br /> $$a = 123;<br /> $b = 123;<br /> echo $this;<br /> echo $b;<br /> $this->getAge();<br /> $b->getAge();<br /> return $this->_name;<br /> }
這段代碼中,$this和$b的值都等於123,但是$this->getAge()可以順利執行,$b->getAge()卻報錯:
Fatal error: Call to a member function getAge() on a non-object...
這是一個詭異的問題,於是可以推斷$this->getAge()和$b->getAge()在編譯之後對應的op handler肯定有所差異,於是查看之。
通過vld查看op的資訊
$this->getAge()對應的為:
ZEND_INIT_METHOD_CALL RES[ IS_UNUSED ] OP1[ IS_UNUSED ] OP2[ IS_CONST (8142027) 'getAge' ]
$b->getAge()在對應的為:
ZEND_INIT_METHOD_CALL RES[ IS_UNUSED ] OP1[ IS_CV !0 ] OP2[ , IS_CONST (8142039) 'getAge' ]
雖然對應了同樣的op code,但是由於運算元的不同(前者不使用OP1,後者使用OP1, 且OP1的值!0表示$b),同一個op code對應同一類handler,然後根據運算元的類型確定到此類handler中的某一個handler。
$this->getAge()對應的handler處理為:ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER
$b->getAge() 對應的handler處理為:ZEND_INIT_METHOD_CALL_SPEC_CV_CONST_HANDLER
這就是它們的差異了,在執行
$my = new Person();
$name = $my->getName();
調用getName的時候,getName()的範圍(scope)已經被設定成$name對象中,文法分析的時候對於getName中$this->getAge()這樣的調用時只是在當前範圍(scope)中調用getAge()函數,不會理會$this的具體值,而在$b->getAge()這樣的調用時,會關心$b所對應的值。