面向對象編程中我們的編程都是圍繞類和對象進行的。那在PHP內部類是怎么實現的呢?它的內存布局以及存儲是怎么樣的呢?繼承、封裝和多態又是怎么實現的呢?
## 類的結構[]()
首先我們看看類是什么。類是用戶定義的一種抽象數據類型,它是現實世界中某些具有共性事物的抽象。有時我們也可以理解其為對象的類別。類也可以看作是一種復合型的結構,其需要存儲多元化的數據,如屬性、方法、以及自身的一些性質等。
類和函數類似,PHP內置及PHP擴展均可以實現自己的內部類,也可以由用戶使用PHP代碼進行定義。當然我們在編寫代碼時通常是自己定義。
使用上,我們使用class關鍵字進行定義,后面接類名,類名可以是任何非PHP保留字的名字。在類名后面緊跟著一對花括號,里面是類的實體,包括類所具有的屬性,這些屬性是對象的狀態的抽象,其表現為PHP中支持的數據類型,也可以包括對象本身,通常我們稱其為成員變量。 除了類的屬性,類的實體中也包括類所具有的操作,這些操作是對象的行為的抽象,其表現為用操作名和實現該操作的方法,通常我們稱其為成員方法或成員函數。看類示例的代碼:
class ParentClass {
}
?
interface Ifce {
public function iMethod();
}
?
final class Tipi extends ParentClass implements Ifce {
public [static](http://www.php.net/static) $sa = 'aaa';
const CA = 'bbb';
?
public function __constrct() {
}
?
public function iMethod() {
}
?
private function _access() {
}
?
public [static](http://www.php.net/static) function access() {
}
}
這里定義了一個父類ParentClass,一個接口Ifce,一個子類Tipi。子類繼承父類ParentClass,實現接口Ifce,并且有一個靜態變量$sa,一個類常量 CA,一個公用方法,一個私有方法和一個公用靜態方法。這些結構在Zend引擎內部是如何實現的?類的方法、成員變量是如何存儲的?訪問控制,靜態成員是如何標記的?
首先,我們看看類的內部存儲結構:
struct _zend_class_entry {
char type; // 類型:ZEND_INTERNAL_CLASS / ZEND_USER_CLASS
char *name;// 類名稱
zend_uint name_length; // 即sizeof(name) - 1
struct _zend_class_entry *parent; // 繼承的父類
int refcount; // 引用數
zend_bool constants_updated;
?
zend_uint ce_flags; // ZEND_ACC_IMPLICIT_ABSTRACT_CLASS: 類存在abstract方法
// ZEND_ACC_EXPLICIT_ABSTRACT_CLASS: 在類名稱前加了abstract關鍵字
// ZEND_ACC_FINAL_CLASS
// ZEND_ACC_INTERFACE
HashTable function_table; // 方法
HashTable default_properties; // 默認屬性
HashTable properties_info; // 屬性信息
HashTable default_static_members;// 類本身所具有的靜態變量
HashTable *static_members; // type == ZEND_USER_CLASS時,取&default_static_members;
// type == ZEND_INTERAL_CLASS時,設為NULL
HashTable constants_table; // 常量
struct _zend_function_entry *builtin_functions;// 方法定義入口
?
?
union _zend_function *constructor;
union _zend_function *destructor;
union _zend_function *clone;
?
?
/* 魔術方法 */
union _zend_function *__get;
union _zend_function *__set;
union _zend_function *__unset;
union _zend_function *__isset;
union _zend_function *__call;
union _zend_function *__tostring;
union _zend_function *serialize_func;
union _zend_function *unserialize_func;
zend_class_iterator_funcs iterator_funcs;// 迭代
?
/* 類句柄 */
zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object,
intby_ref TSRMLS_DC);
?
/* 類聲明的接口 */
int(*interface_gets_implemented)(zend_class_entry *iface,
zend_class_entry *class_type TSRMLS_DC);
?
?
/* 序列化回調函數指針 */
int(*serialize)(zval *object, unsignedchar**buffer, zend_uint *buf_len,
zend_serialize_data *data TSRMLS_DC);
int(*unserialize)(zval **object, zend_class_entry *ce, constunsignedchar*buf,
zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC);
?
?
zend_class_entry **interfaces; // 類實現的接口
zend_uint num_interfaces; // 類實現的接口數
?
?
char *filename; // 類的存放文件地址 絕對地址
zend_uint line_start; // 類定義的開始行
zend_uint line_end; // 類定義的結束行
char *doc_comment;
zend_uint doc_comment_len;
?
?
struct _zend_module_entry *module; // 類所在的模塊入口:EG(current_module)
};
取上面這個結構的部分字段,我們分析文章最開始的那段PHP代碼在內核中的表現。如表5.1所示:
<table><tr><th scope="col">字段名</th> <th scope="col">字段說明 </th> <th scope="col">ParentClass類</th> <th scope="col">Ifce接口</th> <th scope="col">Tipi類</th> </tr><tr><th scope="row">name</th> <td>類名</td> <td>ParentClass</td> <td>Ifce</td> <td>Tipi</td> </tr><tr><th scope="row">type</th> <td>類別</td> <td>2</td> <td>2</td> <td>2</td> </tr><tr><th scope="row">parent</th> <td>父類</td> <td>空</td> <td>空</td> <td>ParentClass類</td> </tr><tr><th scope="row">refcount</th> <td>引用計數</td> <td>1</td> <td>1</td> <td>2</td> </tr><tr><th scope="row">ce_flags</th> <td>類的類型</td> <td>0</td> <td>144</td> <td>524352</td> </tr><tr><th scope="row">function_table</th> <td>函數列表</td> <td>空</td> <td><ul><li>function_name=iMethod | type=2 | fn_flags=258</li><ul/></ul></td> <td><ul><li>function_name=__construct | type=2 | fn_flags=8448 </li><li>function_name=iMethod | type=2 | fn_flags=65800</li><li>function_name=_access | type=2 | fn_flags=66560 </li><li>function_name=access | type=2 | fn_flags=257 </li><ul/></ul></td></tr><tr><th scope="row">interfaces</th> <td>接口列表</td> <td>空</td> <td>空</td> <td>Ifce接口 接口數為1</td> </tr><tr><th scope="row">filename</th> <td>存放文件地址</td> <td>/tipi.php</td> <td>/tipi.php</td> <td>/ipi.php</td> </tr><tr><th scope="row">line_start</th> <td>類開始行數</td> <td>15</td> <td>18</td> <td>22</td> </tr><tr><th scope="row">line_end</th> <td>類結束行數</td> <td>16</td> <td>20</td> <td>38</td> </tr></table>
類的結構中,type有兩種類型,數字標記為1和2。分別為一下宏的定義,也就是說用戶定義的類和模塊或者內置的類也是保存在這個結構里的:
#define ZEND_INTERNAL_CLASS 1
#define ZEND_USER_CLASS 2
對于父類和接口,都是保存在**struct _zend_class_entry**結構體中。這表示接口也是以類的形式存儲,而實現是一樣的,并且在繼承等操作時有與類操作的不同的處理。常規的成員方法存放在函數結構體的哈希表中,而魔術方法則單獨保存。如在類定義中的 union _zend_function *constructor; 定義就是類的構造魔術方法,它是以函數的形式存在于類結構中,并且與常規的方法分隔開來了。在初始化時,這些魔術方法都會被設置為NULL。
## 類的實現[]()
類的定義是以class關鍵字開始,在Zend/zend_language_scanner.l文件中,找到class對應的token為T_CLASS。根據此token,在Zend/zend_language_parser.y文件中,找到編譯時調用的函數:
unticked_class_declaration_statement:
class_entry_type T_STRING extends_from
{ zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }
implements_list
'{'
class_statement_list
'}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
| interface_entry T_STRING
{ zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); } interface_extends_list
'{'
class_statement_list
'}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
;
?
?
class_entry_type:
T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = 0; }
| T_ABSTRACT T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; }
| T_FINAL T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = ZEND_ACC_FINAL_CLASS; }
;
上面的class_entry_type語法說明在語法分析階段將類分為三種類型:常規類(T_CLASS),抽象類(T_ABSTRACT T_CLASS)和final類(T_FINAL T_CLASS )。 他們分別對應的類型在內核中為:
- 常規類(T_CLASS) 對應的type=0
- 抽象類(T_ABSTRACT T_CLASS) 對應type=ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
- final類(T_FINAL T_CLASS) 對應type=ZEND_ACC_FINAL_CLASS
除了上面的三種類型外,類還包含有另外兩種類型沒有加abstract關鍵字的抽象類和接口:
- 沒有加abstract關鍵字的抽象類,它對應的type=ZEND_ACC_IMPLICIT_ABSTRACT_CLASS。由于在class前面沒有abstract關鍵字,在語法分析時并沒有分析出來這是一個抽象類,但是由于類中擁有抽象方法,在函數注冊時判斷成員函數是抽象方法或繼承類中的成員方法是抽象方法時,會將這個類設置為此種抽象類類型。
- 接口,其type=ZEND_ACC_INTERFACE。接口類型的區分是在interface關鍵字解析時設置,見interface_entry:對應的語法說明。
這五種類型在Zend/zend_complie.h文件中定義如下:
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS 0x10
#define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS 0x20
#define ZEND_ACC_FINAL_CLASS 0x40
#define ZEND_ACC_INTERFACE 0x80
常規類為0,在這里沒有定義,并且在程序也是直接賦值為0。
語法解析完后就可以知道一個類是抽象類還是final類,普通的類,又或者接口。定義類時調用了zend_do_begin_class_declaration和zend_do_end_class_declaration函數,從這兩個函數傳入的參數,zend_do_begin_class_declaration函數用來處理類名,類的類別和父類,zend_do_end_class_declaration函數用來處理接口和類的中間代碼這兩個函數在Zend/zend_complie.c文件中可以找到其實現。
在zend_do_begin_class_declaration中,首先會對傳入的類名作一個轉化,統一成小寫,這也是為什么類名不區分大小的原因,如下代碼
<?php
class TIPI {
}
?
class tipi {
?
}
運行時程序報錯: Fatal error: Cannot redeclare class tipi。 這個錯誤會在運行生成中間的代碼時觸發。此錯誤的判斷過程在后面中間代碼生成時說明。而關于類的名稱的判斷則是通過 **T_STRING** token,在語法解析時做的判斷, 但是這只能識別出類名是一個字符串。假如類名為一些關鍵字, 如下代碼:
class self {
}
運行, 程序會顯示: Fatal error: Cannot use 'self' as class name as it is reserved in...
以上的錯誤程序判斷定義在 **zend_do_begin_class_declaration** 函數。與self關鍵字一樣, 還有parent, static兩個關鍵字的判斷在同一個地方。當這個函數執行完后,我們會得到類聲明生成的中間代碼為:**ZEND_DECLARE_CLASS** 。當然,如果我們是聲明內部類的話,則生成的中間代碼為: **ZEND_DECLARE_INHERITED_CLASS**。
根據生成的中間代碼,我們在Zend/zend_vm_execute.h文件中找到其對應的執行函數 **ZEND_DECLARE_CLASS_SPEC_HANDLER**。這個函數通過調用 **do_bind_class** 函數將此類加入到 EG(class_table) 。在添加到列表的同時,也判斷該類是否存在,如果存在,則添加失敗,報我們之前提到的類重復聲明錯誤,只是這個判斷在編譯開啟時是不會生效的。
類相關的各個結構均保存在**struct _zend_class_entry** 結構體中。這些具體的類別在語法分析過程中進行區分。識別出類的類別,類的類名等,并將識別出來的結果存放到類的結構中。
下一節我們一起看看類所包含的成員變量和成員方法。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊