摘要:父類方法為錯誤,成員方法不得被重寫。父子類方法靜態屬性不一致父類方法為非靜態而子類的是靜態或相反,錯誤。
1.類的結構
類是編譯階段的產物,而對象是運行時產生的,它們歸屬于不同階段。編譯完成后我們定義的每個類都會生成一個zend_class_entry,它保存著類的全部信息,在執行階段所有類相關的操作都是用的這個結構,
struct _zend_class_entry { char type; //類的類型:內部類ZEND_INTERNAL_CLASS(1)、用戶自定義類ZEND_USER_CLASS(2) zend_string *name; //類名,PHP類不區分大小寫,統一為小寫 struct _zend_class_entry *parent; //父類 uint32_t ce_flags; //類掩碼,如普通類、抽象類、接口, int default_properties_count; //普通屬性數,包括public、private int default_static_members_count; //靜態屬性數,static HashTable properties_info; //成員屬性基本信息哈希表,key為成員名,value為zend_property_info zval *default_properties_table; //普通屬性值數組 zval *default_static_members_table; //靜態屬性值數組 HashTable function_table; //成員方法哈希表 HashTable constants_table; //常量哈希表,通過constant定義的 //以下是構造函授、析構函數、魔術方法的指針 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 *__callstatic; union _zend_function *__tostring; union _zend_function *__debugInfo; union _zend_function *serialize_func; union _zend_function *unserialize_func; }
類的編譯:首先為類分配一個zend_class_entry結構,如果沒有繼承類則生成一條類聲明的opcode(ZEND_DECLARE_CLASS),有繼承類則生成兩條opcode(ZEND_FETCH_CLASS、ZEND_DECLARE_INHERITED_CLASS),然后再繼續編譯常量、成員屬性、成員方法注冊到zend_class_entry中,最后編譯完成后調用zend_do_early_binding()進行 父子類關聯 以及 注冊到EG(class_table)符號表。
2.類常量
PHP中可以把在類中始終保持不變的值定義為常量,在定義和使用常量的時候不需要使用 $ 符號,常量的值必須是一個定值,它們通過zend_class_entry.constants_table進行存儲,這是一個哈希結構
常量的讀取: class my_class { const A1 = "hi"; } echo my_class::A1; 編譯到echo my_class::A1這行時首先會嘗試檢索下是否已經編譯了my_class,如果能在CG(class_table)中找到,則進一步從類的contants_table查找對應的常量,找到的話則會復制其value替換常量,簡單的講就是類似C語言中的宏,編譯時替換為實際的值了,而不是在運行時再去檢索。 echo my_class::A1; class my_class { const A1 = "hi"; } 在運行時再去檢索。替換成為實際的值
3.成員屬性
屬性中的變量可以初始化,但是初始化的值必須是常數,這里的常數是指PHP腳本在編譯階段時就可以得到其值,而不依賴于運行時的信息才能求值,比如public $time = time();這樣定義一個屬性就會觸發語法錯誤。成員屬性又分為兩類:普通屬性、靜態屬性,與常量的存儲方式不同,成員屬性的初始化值并不是直接用以"屬性名"作為索引的哈希表存儲的,而是通過數組保存的
實際只是成員屬性的VALUE通過數組存儲的,訪問時仍然是根據以"屬性名"為索引的散列表查找具體VALUE的,而這個散列表是zend_class_entry.properties_info
typedef struct _zend_property_info { uint32_t offset; //普通成員變量的內存偏移值,靜態成員變量的數組索引 uint32_t flags; //屬性掩碼,如public、private、protected及是否為靜態屬性 zend_string *name; //屬性名:并不是原始屬性名,private會在原始屬性名前加上類名,protected則會加上*作為前綴 zend_class_entry *ce; //所屬類 } zend_property_info;
成員屬性在類編譯階段就已經分配了zval,靜態與普通的區別在于普通屬性在創建一個對象時還會重新分配zval,對象對普通屬性的操作都是在其自己的空間進行的,各對象隔離,而靜態屬性的操作始終是在類的空間內,各對象共享。
4.成員方法
每個類可以定義若干屬于本類的函數(稱之為成員方法),這種函數與普通的function相同,只是以類的維度進行管理,不是全局性的,所以成員方法保存在類中而不是EG(function_table)
成員方法也有靜態、非靜態之分,靜態方法中不能使用$this,因為其操作的作用域全部都是類的而不是對象的,而非靜態方法中可以通過$this訪問屬于本對象的成員屬性
5.對象的數據結構
typedef struct _zend_object zend_object; struct _zend_object { zend_refcounted_h gc; //引用計數 uint32_t handle; //對象編號 zend_class_entry *ce; //所屬類 const zend_object_handlers *handlers; //對象操作處理函數 HashTable *properties; //普通成員屬性哈希表,用于動態屬性 zval properties_table[1]; //普通屬性值數組 };
對象的創建:首先是根據類名在EG(class_table)中查找對應zend_class_entry、然后是創建并初始化一個對象、最后是初始化調用構造函數的zend_execute_data
實例化一個對象: step1: 首先根據類名去EG(class_table)中找到具體的類,即zend_class_entry step2: 分配zend_object結構,一起分配的還有普通非靜態屬性值的內存 step3: 初始化對象的非靜態屬性,將屬性值從zend_class_entry淺復制(寫時分離)到對象中 step4: 查找當前類是否定義了構造函數,如果沒有定義則跳過執行構造函數的opcode,否則為調用構造函數的執行進行一些準備工作(分配zend_execute_data) step5: 實例化完成,返回新實例化的對象(如果返回的對象沒有變量使用則直接釋放掉了)
6.繼承
(a).繼承屬性
屬性從父類復制到子類 。子類會將父類的公共、受保護的屬性值數組全部合并到子類中,然后將全部屬性的zend_property_info哈希表也合并到子類中
(b).繼承常量
常量的合并策略比較簡單,如果父類與子類沖突時用子類的,不沖突時則將父類的常量合并到子類。
(c).繼承方法
與屬性一樣,子類可以繼承父類的公有、受保護的方法,方法的繼承比較復雜,因為會有訪問控制、抽象類、接口、Trait等多種限制條件。實現上與前面幾種相同,即父類的function_table合并到子類的function_table中。
如果父類是用戶自定義的類,且繼承的方法沒有靜態變量則不會硬拷貝,而是增加zend_function的引用計數(zend_op_array.refcount)。
子類重寫了父類的方法的檢查規則 (1)抽象子類的抽象方法與抽象父類的抽象方法沖突: 無法重寫,Fatal錯誤。 (2)父類方法為final: Fatal錯誤,final成員方法不得被重寫。 (3)父子類方法靜態屬性不一致: 父類方法為非靜態而子類的是靜態(或相反),Fatal錯誤。 (4)抽象子類的抽象方法覆蓋父類非抽象方法: Fatal錯誤。 (5)子類方法限制父類方法訪問權限: Fatal錯誤,不允許派生類限制父類方法的訪問權限,如父類方法為public, 而子類試圖重寫為protected/private。 6)剩余檢查情況: 除了上面5中情形下無法重寫方法,剩下還有一步對函數參數的檢查
7. 動態屬性
class my_class { public $id = 123; public function test($name, $value){ $this->$name = $value; } }
非靜態成員屬性值在實例化時保存到了對象中,屬性的操作按照編譯時按順序編好的序號操作,各對象對其非靜態成員屬性的操作互不干擾,而動態屬性是在運行時創建的,動態創建的屬性保存在zend_object->properties哈希表中
屬性查找:首先按照普通屬性在zend_class_entry.properties_info找,沒有找到再去zend_object->properties繼續查找
首次創建動態屬性將通過rebuild_object_properties()初始化zend_object->properties哈希表,后面再創建動態屬性直接插入此哈希表,rebuild_object_properties()過程并不僅僅是創建一個HashTable,還會將普通成員屬性值插入到這個數組中,與動態屬性不同,這里的插入并不是增加原zend_value的refcount,而是創建了一個IS_INDIRECT類型的zval,指向原屬性值zval
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/28585.html
1.EG(executor_globals/zend_executor_globals) PHP整個生命周期中最主要的一個結構,是一個全局變量,在main執行前分配(非ZTS下),直到PHP退出,它記錄著當前請求全部的信息 showImg(https://segmentfault.com/img/bV8fW0?w=960&h=777); 2.EX(execute_data/zend_execut...
摘要:引擎中定義了很多內部函數供用戶在中使用,比如等等,除了引擎中定義的內部函數,擴展中也提供了大量內部函數,我們也可以靈活的通過擴展自行定制。頭部是一個與完全相同的結構函數指針,展開 1.函數的存儲結構 typedef union _zend_function zend_function; union _zend_function { zend_uchar typ...
摘要:但在多線程模式下會有多個,也就是說每個線程都有一個獨立的內存池內存分配分配超過內存的申請,與通用的內存申請沒有太大差別,只是將申請的內存塊通過單鏈表進行了管理。的分配實際就是分配多個,的分配也是內存分配的基礎,它是向系統申請內存的唯一粒度。 1.Zend內存池 內存池是內核中最底層的內存操作,定義了三種粒度的內存塊:chunk、page、slot,每個chunk的大小為2M,page大...
摘要:代碼的編譯的解析過程任務就是將代碼轉化為數組,代碼里的所有信息都保存在數組中,然后將數組交給引擎執行,就是內核具體執行的命令,比如賦值加減操作函數調用等,每一條都對應一個處理,這些是提前定義好的函數。 1.PHP代碼的編譯 PHP的解析過程任務就是將PHP代碼轉化為opcode數組,代碼里的所有信息都保存在opcode數組中,然后將opcode數組交給zend引擎執行,opcode就是...
摘要:插入一個元素時先將元素按先后順序插入數組,位置是,再根據的哈希值映射到散列表中的某個位置,將存入這個位置查找時先在散列表中映射到,得到在數組的位置,再從數組中取出元素。目前只有兩種類型會使用這種機制。 1.變量結構 typedef struct _zval_struct zval; typedef union _zend_value { zend_long ...
閱讀 569·2023-04-26 02:58
閱讀 2309·2021-09-27 14:01
閱讀 3616·2021-09-22 15:57
閱讀 1175·2019-08-30 15:56
閱讀 1049·2019-08-30 15:53
閱讀 796·2019-08-30 15:52
閱讀 651·2019-08-26 14:01
閱讀 2167·2019-08-26 13:41