在最開始接觸PHP的時候,都是面向過程的方法來自己做一些很簡單的網站在玩,寫PHP代碼就是堆砌,拓展性與維護性太差改個邏輯極不方便。後來發現PHP是支援物件導向的,忽然覺得自己那是後還真是年輕,真是孤陋寡聞呀,畢竟PHP是用C來實現,也不足為奇。
前言: 從我們接觸
PHP 開始,我們最先遇到的是函數:數組操作函數,字串操作函數,檔案操作函數等等。 這些函數是我們使用PHP的基礎,也是PHP自出生就支援的面向過程編程。面向過程將一個個功能封裝, 以一種模組化的思想解決問題。 從PHP4起開始支援物件導向編程。但PHP4的物件導向支援不太完善。 從PHP5起,PHP引入了新的物件模型(Object Model),增加了許多新特性,包括存取控制、 抽象類別和final類、類方法、魔術方法、介面、對象複製和類型提示等。
並且在近期發布的PHP5.3版本中,針對物件導向編程增加了命名空間、延遲靜態繫結 以及增加了兩個魔術方法__callStatic()和__invoke()。 那麼,在PHP底層,其是怎麼實現的呢,其結構如何。
一。類的結構 引用TIPI的一個案例:
class ParentClass {} interface Ifce { public function iMethod();} final class Tipi extends ParentClass implements Ifce { public static $sa = 'aaa'; const CA = 'bbb'; public function __constrct() { } public function iMethod() { } private function _access() { } public 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代碼在核心中的表現。 如下 所示:
| 欄位名 |
欄位說明 |
ParentClass類 |
Ifce介面 |
Tipi類 |
| name |
類名 |
ParentClass |
Ifce |
Tipi |
| type |
類別 |
2(使用者自訂) |
2 (使用者自訂) |
2 (使用者自訂,1為系統內建類) |
| parent |
父類 |
空 |
空 |
ParentClass類 |
| refcount |
引用計數 |
1 |
1 |
2 |
| ce_flags |
類的類型 |
0 |
144 |
524352 |
| function_table |
函數列表 |
空 |
function_name=iMethod | type=2 | fn_flags=258 |
function_name=__construct | type=2 | fn_flags=8448
function_name=iMethod | type=2 | fn_flags=65800
function_name=_access | type=2 | fn_flags=66560
function_name=access | type=2 | fn_flags=257 |
| interfaces |
介面列表 |
空 |
空 |
Ifce介面 介面數為1 |
| filename |
存放檔案地址 |
/tipi.php |
/tipi.php |
/ipi.php |
| line_start |
類開始行數 |
15 |
18 |
22 |
| line_end |
類結束行數 |
16 |
20 |
38 |
二。變數與成員變數 如 PHP核心的儲存機制(分離/改變)所介紹, 變數要麼是定義在全域範圍中,叫做全域變數,要麼是定義在某個函數中, 叫做局部變數。 成員變數是定義在類裡面,並和成員方法處於同一層次。如下一個簡單的PHP程式碼範例,定義了一個類, 並且這個類有一個成員變數。
class Tipi { public $var;} 1.成員變數的訪問: 訪問這個成員變數當然是通過對象來訪問。 2.成員變數的規則: 1.介面中不允許使用成員變數 2.成員變數不能擁有抽象屬性 3.不能聲明成員變數為final 4.不能重複聲明屬性 在聲明類的時候初始化了類的成員變數所在的HashTable,之後如果有新的成員變數聲明時,在編譯時間zend_do_declare_property。函數首先檢查成員變數不允許的這4 條情況。 比如:.
class Tipi { public final $var;} 運行程式將報錯,違反了第三條:Fatal error: Cannot declare property Tipi::$var final, the final modifier is allowed only for methods and classes in .. 這個錯誤由zend_do_declare_property函數拋出
三。函數與成員方法 成員方法從本質上來講也是一種函數,所以其儲存結構也和常規函數一樣,儲存在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函數中的代碼。
四。方法(Function)與函數(Method)的異同 在前面介紹了函數的實現,函數與方法的本質是比較相似的,都是將一系列的邏輯放到一個集合裡執行, 但二者在使用中也存在很多的不同,這裡我們討論一下二者的實現。 從實現的角度來看,二者內部代碼都被最終解釋為op_array,其執行是沒有區別的(除非使用了$this/self等對象特有的變法或方法), 而二者的不同體現在兩個方面: 1.是定義(註冊)的實現; 2.是調用的實現;
定義(註冊)方式的實現 函數和方法都是在編譯階段註冊到compiler_globals變數中的,二者都使用相同的核心處理函數zend_do_begin_function_declaration() 和zend_do_end_function_declaration()來完成這一過程。 二者的內部內容會被最終解釋並儲存為一個op_codes數組,但編譯後“掛載”的位置不同,如下圖:
PHP中函數與方法的註冊位置
調用方式的實現
定義位置的不同,以及性質的不同,決定了方法比函數要進行更多的驗證工作, 方法的調用比函數的調用多一個名為
ZEND_INIT_METHOD_CALL的OPCODE, 其作用是把方法註冊到execute_data.fbc , 然後就可以使用與函數相同的處理函數
ZEND_DO_FCALL_BY_NAME進行處理。