PHP內核探索:類的成員方法

成員方法從本質上來講也是一種函數
服務器君一共花費了271.467 ms進行了7次數據庫查詢,努力地為您提供了這個頁面。
試試閱讀模式?希望聽取您的建議

成員方法從本質上來講也是一種函數,所以其存儲結構也和常規函數一樣,存儲在zend_function結構體中。 對于一個類的多個成員方法,它是以HashTable的數據結構存儲了多個zend_function結構體。 和前面的成員變量一樣,在類聲明時成員方法也通過調用zend_initialize_class_data方法,初始化了整個方法列表所在的HashTable。 在類中我們如果要定義一個成員方法,格式如下:

class Tipi{
    public function t() {
        echo 1;
    }
}

除去訪問控制關鍵字,一個成員方法和常規函數是一樣的,從語法解析中調用的函數一樣(都是zend_do_begin_function_declaration函數), 但是其調用的參數有一些不同,第三個參數is_method,成員方法的賦值為1,表示它作為成員方法的屬性。 在這個函數中會有一系統的編譯判斷,比如在接口中不能聲明私有的成員方法。 看這樣一段代碼:

interface Ifce {
   private function method();
}

如果直接運行,程序會報錯:Fatal error: Access type for interface method Ifce::method() must be omitted in 這段代碼對應到zend_do_begin_function_declaration函數中的代碼,如下:

if (is_method) {
    if (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) {
        if ((Z_LVAL(fn_flags_znode->u.constant) & ~(ZEND_ACC_STATIC|ZEND_ACC_PUBLIC))) {
            zend_error(E_COMPILE_ERROR, "Access type for interface method %s::%s() must be omitted",
                CG(active_class_entry)->name, function_name->u.constant.value.str.val);
        }
        Z_LVAL(fn_flags_znode->u.constant) |= ZEND_ACC_ABSTRACT; /* propagates to the rest of the parser */
    }
    fn_flags = Z_LVAL(fn_flags_znode->u.constant); /* must be done *after* the above check */
} else {
    fn_flags = 0;
}

在此程序判斷后,程序將方法直接添加到類結構的function_talbe字段,在此之后,又是若干的編譯檢測。 比如接口的一些魔術方法不能被設置為非公有,不能被設置為static,如__call()、__callStatic()、__get()等。 如果在接口中設置了靜態方法,如下定義的一個接口:

interface ifce {
    public static function __get();
}

若運行這段代碼,則會顯示Warning:Warning: The magic method __get() must have public visibility and cannot be static in

這段編譯檢測在zend_do_begin_function_declaration函數中對應的源碼如下:

if (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) {
        if ((name_len == sizeof(ZEND_CALL_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) {
            if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
                zend_error(E_WARNING, "The magic method __call() must have public visibility and cannot be static");
            }
        } else if() {   //  其它魔術方法的編譯檢測
        }
}

同樣,對于類中的這些魔術方法,也有同樣的限制,如果在類中定義了靜態的魔術方法,則顯示警告。如下代碼:

class Tipi {
    public static function __get($var) {
 
    }
}

運行這段代碼,則會顯示: Warning: The magic method __get() must have public visibility and cannot be static in

與成員變量一樣,成員方法也有一個返回所有成員方法的函數--get_class_methods()。 此函數返回由指定的類中定義的方法名所組成的數組。 從 PHP 4.0.6 開始,可以指定對象本身來代替指定的類名。 它屬于PHP內建函數,整個程序流程就是一個遍歷類成員方法列表,判斷是否為符合條件的方法, 如果是,則將這個方法作為一個元素添加到返回數組中。

靜態成員方法

類的靜態成員方法通常也叫做類方法。 與靜態成員變量不同,靜態成員方法與成員方法都存儲在類結構的function_table 字段。

類的靜態成員方法可以通過類名直接訪問。

class Tipi{
    public static function t() {
        echo 1;
    }
}
 
Tipi::t();

以上的代碼在VLD擴展下生成的部分中間代碼如如下:

number of ops:  8
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   2     0  >   EXT_STMT
         1      NOP
   8     2      EXT_STMT
         3      ZEND_INIT_STATIC_METHOD_CALL                             'Tipi','t'
         4      EXT_FCALL_BEGIN
         5      DO_FCALL_BY_NAME                              0
         6      EXT_FCALL_END
   9     7    > RETURN                                                   1
 
branch: #  0; line:     2-    9; sop:     0; eop:     7
path #1: 0,
Class Tipi:
Function t:
Finding entry points
Branch analysis from position: 0

從以上的內容可以看出整個靜態成員方法的調用是一個先查找方法,再調用的過程。 而對于調用操作,對應的中間代碼為 ZEND_INIT_STATIC_METHOD_CALL。由于類名和方法名都是常量, 于是我們可以知道中間代碼對應的函數是ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER。 在這個函數中,它會首先調用zend_fetch_class函數,通過類名在EG(class_table)中查找類,然后再執行靜態方法的獲取方法。

if (ce->get_static_method) {
    EX(fbc) = ce->get_static_method(ce, function_name_strval, function_name_strlen TSRMLS_CC);
} else {
    EX(fbc) = zend_std_get_static_method(ce, function_name_strval, function_name_strlen TSRMLS_CC);
}

如果類結構中的get_static_method方法存在,則調用此方法,如果不存在,則調用zend_std_get_static_method。 在PHP的源碼中get_static_method方法一般都是NULL,這里我們重點查看zend_std_get_static_method函數。 此函數會查找ce->function_table列表,在查找到方法后檢查方法的訪問控制權限,如果不允許訪問,則報錯,否則返回函數結構體。 關于訪問控制,我們在后面的小節中說明。

靜態方法和實例方法的小漏洞

細心的讀者應該注意到前面提到靜態方法和實例方法都是保存在類結構體zend_class_entry.function_table中,那這樣的話, Zend引擎在調用的時候是怎么區分這兩類方法的,比如我們靜態調用實例方法或者實例調用靜態方法會怎么樣呢?

可能一般人不會這么做,不過筆者有一次錯誤的這樣調用了,而代碼沒有出現任何問題, 在review代碼的時候意外發現筆者像實例方法那樣調用的靜態方法,而什么問題都沒有發生(沒有報錯)。 在理論上這種情況是不應發生的,類似這這樣的情況在PHP中是非常的多的,例如前面提到的create_function方法返回的偽匿名方法, 后面介紹訪問控制時還會介紹訪問控制的一些瑕疵,PHP在現實中通常采用Quick and Dirty的方式來實現功能和解決問題, 這一點和Ruby完整的面向對象形成鮮明的對比。我們先看一個例子:

<?php
 
error_reporting(E_ALL);
 
class A {
    public static function staticFunc() {
        echo "static";
    }
 
    public function instanceFunc() {
        echo "instance";    
    }
}
 
A::instanceFunc(); // instance
$a = new A();
$a->staticFunc();  // static
?>

上面的代碼靜態的調用了實例方法,程序輸出了instance,實例調用靜態方法也會正確輸出static,這說明這兩種方法本質上并沒有卻別。 唯一不同的是他們被調用的上下文環境,例如通過實例方法調用方法則上下文中將會有$this這個特殊變量,而在靜態調用中將無法使用$this變量。

不過實際上Zend引擎是考慮過這個問題的,將error_reporting的級別增加E_STRICT,將會出出現E_STRICT錯誤:

Strict Standards: Non-static method A::instanceFunc() should not be called statically

這只是不建議將實例方法靜態調用,而對于實例調用靜態方法沒有出現E_STRICT錯誤,有人說:某些事情可以做并不代表我們要這樣做。

PHP在實現新功能時通常采用漸進的方式,保證兼容性,在具體實現上通常采用打補丁的方式,這樣就造成有些”邊界“情況沒有照顧到。

延伸閱讀

此文章所在專題列表如下:

  1. PHP內核探索:從SAPI接口開始
  2. PHP內核探索:一次請求的開始與結束
  3. PHP內核探索:一次請求生命周期
  4. PHP內核探索:單進程SAPI生命周期
  5. PHP內核探索:多進程/線程的SAPI生命周期
  6. PHP內核探索:Zend引擎
  7. PHP內核探索:再次探討SAPI
  8. PHP內核探索:Apache模塊介紹
  9. PHP內核探索:通過mod_php5支持PHP
  10. PHP內核探索:Apache運行與鉤子函數
  11. PHP內核探索:嵌入式PHP
  12. PHP內核探索:PHP的FastCGI
  13. PHP內核探索:如何執行PHP腳本
  14. PHP內核探索:PHP腳本的執行細節
  15. PHP內核探索:操作碼OpCode
  16. PHP內核探索:PHP里的opcode
  17. PHP內核探索:解釋器的執行過程
  18. PHP內核探索:變量概述
  19. PHP內核探索:變量存儲與類型
  20. PHP內核探索:PHP中的哈希表
  21. PHP內核探索:理解Zend里的哈希表
  22. PHP內核探索:PHP哈希算法設計
  23. PHP內核探索:翻譯一篇HashTables文章
  24. PHP內核探索:哈希碰撞攻擊是什么?
  25. PHP內核探索:常量的實現
  26. PHP內核探索:變量的存儲
  27. PHP內核探索:變量的類型
  28. PHP內核探索:變量的值操作
  29. PHP內核探索:變量的創建
  30. PHP內核探索:預定義變量
  31. PHP內核探索:變量的檢索
  32. PHP內核探索:變量的類型轉換
  33. PHP內核探索:弱類型變量的實現
  34. PHP內核探索:靜態變量的實現
  35. PHP內核探索:變量類型提示
  36. PHP內核探索:變量的生命周期
  37. PHP內核探索:變量賦值與銷毀
  38. PHP內核探索:變量作用域
  39. PHP內核探索:詭異的變量名
  40. PHP內核探索:變量的value和type存儲
  41. PHP內核探索:全局變量Global
  42. PHP內核探索:變量類型的轉換
  43. PHP內核探索:內存管理開篇
  44. PHP內核探索:Zend內存管理器
  45. PHP內核探索:PHP的內存管理
  46. PHP內核探索:內存的申請與銷毀
  47. PHP內核探索:引用計數與寫時復制
  48. PHP內核探索:PHP5.3的垃圾回收機制
  49. PHP內核探索:內存管理中的cache
  50. PHP內核探索:寫時復制COW機制
  51. PHP內核探索:數組與鏈表
  52. PHP內核探索:使用哈希表API
  53. PHP內核探索:數組操作
  54. PHP內核探索:數組源碼分析
  55. PHP內核探索:函數的分類
  56. PHP內核探索:函數的內部結構
  57. PHP內核探索:函數結構轉換
  58. PHP內核探索:定義函數的過程
  59. PHP內核探索:函數的參數
  60. PHP內核探索:zend_parse_parameters函數
  61. PHP內核探索:函數返回值
  62. PHP內核探索:形參return value
  63. PHP內核探索:函數調用與執行
  64. PHP內核探索:引用與函數執行
  65. PHP內核探索:匿名函數及閉包
  66. PHP內核探索:面向對象開篇
  67. PHP內核探索:類的結構和實現
  68. PHP內核探索:類的成員變量
  69. PHP內核探索:類的成員方法
  70. PHP內核探索:類的原型zend_class_entry
  71. PHP內核探索:類的定義
  72. PHP內核探索:訪問控制
  73. PHP內核探索:繼承,多態與抽象類
  74. PHP內核探索:魔術函數與延遲綁定
  75. PHP內核探索:保留類與特殊類
  76. PHP內核探索:對象
  77. PHP內核探索:創建對象實例
  78. PHP內核探索:對象屬性讀寫
  79. PHP內核探索:命名空間
  80. PHP內核探索:定義接口
  81. PHP內核探索:繼承與實現接口
  82. PHP內核探索:資源resource類型
  83. PHP內核探索:Zend虛擬機
  84. PHP內核探索:虛擬機的詞法解析
  85. PHP內核探索:虛擬機的語法分析
  86. PHP內核探索:中間代碼opcode的執行
  87. PHP內核探索:代碼的加密與解密
  88. PHP內核探索:zend_execute的具體執行過程
  89. PHP內核探索:變量的引用與計數規則
  90. PHP內核探索:新垃圾回收機制說明

本文地址:http://www.824886.live/librarys/veda/detail/1500,歡迎訪問原出處。

不打個分嗎?

轉載隨意,但請帶上本文地址:

http://www.824886.live/librarys/veda/detail/1500

如果你認為這篇文章值得更多人閱讀,歡迎使用下面的分享功能。
小提示:您可以按快捷鍵 Ctrl + D,或點此 加入收藏。

大家都在看

閱讀一百本計算機著作吧,少年

很多人覺得自己技術進步很慢,學習效率低,我覺得一個重要原因是看的書少了。多少是多呢?起碼得看3、4、5、6米吧。給個具體的數量,那就100本書吧。很多人知識結構不好而且不系統,因為在特定領域有一個足夠量的知識量+足夠良好的知識結構,系統化以后就足以應對大量未曾遇到過的問題。

奉勸自學者:構建特定領域的知識結構體系的路徑中再也沒有比學習該專業的專業課程更好的了。如果我的知識結構體系足以囊括面試官的大部分甚至吞并他的知識結構體系的話,讀到他言語中的一個詞我們就已經知道他要表達什么,我們可以讓他坐“上位”畢竟他是面試官,但是在知識結構體系以及心理上我們就居高臨下。

所以,閱讀一百本計算機著作吧,少年!

《UNIX環境高級編程(第2版)》 史蒂文斯 (作者), 拉戈 (作者), 尤晉元 (譯者), 張亞英 (譯者), 戚正偉 (譯者)

《UNIX環境高級編程(第2版)》是被譽為UNIX編程“圣經”的Advanced Programming in the UNIX Environment一書的更新版。在本書第1版出版后的十幾年中,UNIX行業已經有了巨大的變化,特別是影響UNIX編程接口的有關標準變化很大。本書在保持了前一版的風格的基礎上,根據最新的標準對內容進行了修訂和增補,反映了最新的技術發展。書中除了介紹UNIX文件和目錄、標準I/O庫、系統數據文件和信息、進程環境、進程控制、進程關系、信號、線程、線程控制、守護進程、各種I/O、進程間通信、網絡IPC、偽終端等方面的內容,還在此基礎上介紹了多個應用示例,包括如何創建數據庫函數庫以及如何與網絡打印機通信等。

更多計算機寶庫...

云南快乐十分走势一定牛 下载云南快乐十分开奖结果 中国最大的股票配资公司 在线pk10官网开奖结果 重庆时时软件免费 体彩浙江6+1开奖时间 江苏快3开奖结果今 极速快三辅助器 体彩11选5最聪明的玩法 股票权重是什么意思 天天红包赛怎么进入