<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ### 3.4.2 對象 對象是類的實例,PHP中要創建一個類的實例,必須使用 new 關鍵字。類應在被實例化之前定義(某些情況下則必須這樣,比如3.4.1最后那幾個例子)。 #### 3.4.2.1 對象的數據結構 對象的數據結構非常簡單: ```c 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]; //普通屬性值數組 }; ``` 幾個主要的成員: __(1)handle:__ 一次request期間對象的編號,每個對象都有一個唯一的編號,與創建先后順序有關,主要在垃圾回收時用,下面會詳細說明。 __(2)ce:__ 所屬類的zend_class_entry。 __(3)handlers:__ 這個保存的對象相關操作的一些函數指針,比如成員屬性的讀寫、成員方法的獲取、對象的銷毀/克隆等等,這些操作接口都有默認的函數。 ```c struct _zend_object_handlers { int offset; zend_object_free_obj_t free_obj; //釋放對象 zend_object_dtor_obj_t dtor_obj; //銷毀對象 zend_object_clone_obj_t clone_obj;//復制對象 zend_object_read_property_t read_property; //讀取成員屬性 zend_object_write_property_t write_property;//修改成員屬性 ... } //默認值處理handler ZEND_API zend_object_handlers std_object_handlers = { 0, zend_object_std_dtor, /* free_obj */ zend_objects_destroy_object, /* dtor_obj */ zend_objects_clone_obj, /* clone_obj */ zend_std_read_property, /* read_property */ zend_std_write_property, /* write_property */ zend_std_read_dimension, /* read_dimension */ zend_std_write_dimension, /* write_dimension */ zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */ NULL, /* get */ NULL, /* set */ zend_std_has_property, /* has_property */ zend_std_unset_property, /* unset_property */ zend_std_has_dimension, /* has_dimension */ zend_std_unset_dimension, /* unset_dimension */ zend_std_get_properties, /* get_properties */ zend_std_get_method, /* get_method */ NULL, /* call_method */ zend_std_get_constructor, /* get_constructor */ zend_std_object_get_class_name, /* get_class_name */ zend_std_compare_objects, /* compare_objects */ zend_std_cast_object_tostring, /* cast_object */ NULL, /* count_elements */ zend_std_get_debug_info, /* get_debug_info */ zend_std_get_closure, /* get_closure */ zend_std_get_gc, /* get_gc */ NULL, /* do_operation */ NULL, /* compare */ } ``` > __Note:__ 這些handler用于操作對象(如:設置、讀取屬性),std_object_handlers是PHP定義的默認、標準的處理函數,在擴展中可以自定義handler,比如:重定義write_property,這樣設置一個對象的屬性時將調用擴展自己定義的處理函數,讓擴展擁有了更高的控制權限。 > > 需要注意的是:const zend_object_handlers *handlers,這里的handlers指針加了const修飾符,const修飾的是handlers**指向的對象**,而不是handlers指針本身,所以擴展中可以將一個對象的handlers修改為另一個zend_object_handlers指針,但無法修改zend_object_handlers中的值,比如:`obj->handlers->write_property = xxx`將報錯,而:`obj->handlers = xxx`則是可以的。 __(4)properties:__ 普通成員屬性哈希表,對象創建之初這個值為NULL,主要是在動態定義屬性時會用到,與properties_table有一定關系,下一節我們將單獨說明,這里暫時忽略。 __(5)properties_table:__ 成員屬性數組,還記得我們在介紹類一節時提過非靜態屬性存儲在對象結構中嗎?就是這個properties_table!注意,它是一個數組,`zend_object`是個變長結構體,分配時會根據非靜態屬性的數量確定其大小。 #### 3.4.2.2 對象的創建 PHP中通過`new + 類名`創建一個類的實例,我們從一個例子分析下對象創建的過程中都有哪些操作。 ```php class my_class { const TYPE = 90; public $name = "pangudashu"; public $ids = array(); } $obj = new my_class(); ``` 類的定義就不用再說了,我們只看`$obj = new my_class();`這一句,這條語句包括兩部分:實例化類、賦值,下面看下實例化類的語法規則: ```c new_expr: T_NEW class_name_reference ctor_arguments { $$ = zend_ast_create(ZEND_AST_NEW, $2, $3); } | T_NEW anonymous_class { $$ = $2; } ; ``` 從語法規則可以很直觀的看出此語法的兩個主要部分:類名、參數列表,編譯器在解析到實例化類時就創建一個`ZEND_AST_NEW`類型的節點,后面編譯為opcodes的過程我們不再細究,這里直接看下最終生成的opcodes。 ![](../img/object_new_op.png) 你會發現實例化類產生了兩條opcode(實際可能還會更多):ZEND_NEW、ZEND_DO_FCALL,除了創建對象的操作還有一條函數調用的,沒錯,那條就是調用`構造方法`的操作。 根據opcode、操作數類型可知`ZEND_NEW`對應的處理handler為`ZEND_NEW_SPEC_CONST_HANDLER()`: ```c static int ZEND_NEW_SPEC_CONST_HANDLER(zend_execute_data *execute_data) { zval object_zval; zend_function *constructor; zend_class_entry *ce; ... //第1步:根據類名查找zend_class_entry ce = zend_fetch_class_by_name(Z_STR_P(EX_CONSTANT(opline->op1)), ...); ... //第2步:創建&初始化一個這個類的對象 if (UNEXPECTED(object_init_ex(&object_zval, ce) != SUCCESS)) { HANDLE_EXCEPTION(); } //第3步:獲取構造方法 //獲取構造方法函數,實際就是直接取zend_class_entry.constructor //get_constructor => zend_std_get_constructor() constructor = Z_OBJ_HT(object_zval)->get_constructor(Z_OBJ(object_zval)); if (constructor == NULL) { ... //此opcode之后還有傳參、調用構造方法的操作 //所以如果沒有定義構造方法則直接跳過這些操作 ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2)); }else{ //定義了構造方法 //初始化調用構造函數的zend_execute_data zend_execute_data *call = zend_vm_stack_push_call_frame(...); call->prev_execute_data = EX(call); EX(call) = call; ... } } ``` 從上面的創建對象的過程看整個流程主要分為三步:首先是根據類名在EG(class_table)中查找對應zend_class_entry、然后是創建并初始化一個對象、最后是初始化調用構造函數的zend_execute_data。 我們再具體看下第2步創建、初始化對象的操作,`object_init_ex(&object_zval, ce)`最終調用的是`_object_and_properties_init()`。 ```c //zend_API.c ZEND_API int _object_and_properties_init(zval *arg, zend_class_entry *class_type, ...) { //檢查類是否可以實例化 ... //用戶自定義的類create_object都是NULL //只有PHP幾個內部的類有這個值,比如exception、error等 if (class_type->create_object == NULL) { //分配一個對象 ZVAL_OBJ(arg, zend_objects_new(class_type)); ... //初始化成員屬性 object_properties_init(Z_OBJ_P(arg), class_type); } else { //調用自定義的創建object的鉤子函數 ZVAL_OBJ(arg, class_type->create_object(class_type)); } return SUCCESS; } ``` 還記得上一節介紹zend_class_entry時有幾個自定義的鉤子函數嗎?如果定義了`create_object`這個地方就會調用自定義的函數來創建zend_object,這種情況通常發生在內核或擴展中定義的內部類(當然用戶自定義類也可以修改,但一般不會那樣做);用戶自定義類在這個地方又具體分了兩步:分配對象結構、初始化成員屬性,我們繼續看下這里面的處理。 __(1)分配對象結構:zend_object__ ```c //zend_objects.c ZEND_API zend_object *zend_objects_new(zend_class_entry *ce) { //分配zend_object zend_object *object = emalloc(sizeof(zend_object) + zend_object_properties_size(ce)); zend_object_std_init(object, ce); //設置對象的操作handler為std_object_handlers object->handlers = &std_object_handlers; return object; } ``` 有個地方這里需要特別注意:分配對象結構的內存并不僅僅是zend_object的大小。我們在3.4.2.1介紹properties_table時說過這是一個變長數組,它用來存放非靜態屬性的值,所以分配zend_object時需要加上非靜態屬性所占用的內存大小:`zend_object_properties_size()`,根據普通非靜態屬性個數確定,如果沒有定義__get()、__set()等魔術方法則占用內存就是: __屬性數*sizeof(zval)__ ,如果定義了這些魔術方法那么會多分配一個zval的空間,這個多出來zval的用途下面介紹成員屬性的讀寫時再作說明。 另外這里還有一個關鍵操作:__將object編號并插入EG(objects_store).object_buckets數組__。zend_object有個成員:handle,這個值在一次request期間所有實例化對象的編號,每調用`zend_objects_new()`實例化一個對象就會將其插入到object_buckets數組中,其在數組中的下標就是handle。這個過程是在`zend_objects_store_put()`中完成的。 ```c //zend_objects_API.c ZEND_API void zend_objects_store_put(zend_object *object) { int handle; if (EG(objects_store).free_list_head != -1) { //這種情況主要是gc中會將中間一些object銷毀,空出一些bucket位置 //然后free_list_head就指向了第一個可用的bucket位置 //后面可用的保存在第一個空閑bucket的handle中 handle = EG(objects_store).free_list_head; EG(objects_store).free_list_head = GET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[handle]); } else { if (EG(objects_store).top == EG(objects_store).size) { //擴容 } //遞增加1 handle = EG(objects_store).top++; } object->handle = handle; //存入object_buckets數組 EG(objects_store).object_buckets[handle] = object; } typedef struct _zend_objects_store { zend_object **object_buckets; //對象數組 uint32_t top; //當前全部object數 uint32_t size; //object_buckets大小 int free_list_head; //第一個可用object_buckets位置 } zend_objects_store; ``` 將所有的對象保存在`EG(objects_store).object_buckets`中的目的是用于垃圾回收(不確定是不是還有其它的作用),防止出現循環引用而導致內存泄漏的問題,這個機制后面章節會單獨介紹,這里只要記得有這么個東西就行了。 __(2)初始化成員屬性__ ```c ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type) { if (class_type->default_properties_count) { zval *src = class_type->default_properties_table; zval *dst = object->properties_table; zval *end = src + class_type->default_properties_count; //將非靜態屬性值從: //zend_class_entry.default_properties_table復制到zend_object.properties_table do { ZVAL_COPY(dst, src); src++; dst++; } while (src != end); object->properties = NULL; } } ``` 這一步操作是將非靜態屬性的值從`zend_class_entry.default_properties_table -> zend_object.properties_table`,當然這里不是硬拷貝,而是淺復制(增加引用),兩者當前指向的value還是同一份,除非對象試圖改寫指向的屬性值,那時將觸發寫時復制機制重新拷貝一份。 上面那個例子,類有兩個普通屬性:$name、$ids,假如我們實例化了兩個對象,那么zend_class_entry與zend_object中普通屬性值的關系如下圖所示。 ![](../img/object_class_prop.png) 以上就是實例化一個對象的過程,總結一下具體的步驟: * __step1:__ 首先根據類名去EG(class_table)中找到具體的類,即zend_class_entry * __step2:__ 分配zend_object結構,一起分配的還有普通非靜態屬性值的內存 * __step3:__ 初始化對象的非靜態屬性,將屬性值從zend_class_entry淺復制到對象中 * __step4:__ 查找當前類是否定義了構造函數,如果沒有定義則跳過執行構造函數的opcode,否則為調用構造函數的執行進行一些準備工作(分配zend_execute_data) * __step5:__ 實例化完成,返回新實例化的對象(如果返回的對象沒有變量使用則直接釋放掉了) #### 3.4.2.3 成員屬性的讀寫 普通成員屬性的讀寫處理handler分別為`zend_object.handlers`中的:read_property、write_property,默認對應的函數為:zend_std_read_property()、zend_std_write_property(),訪問獲取修改一個普通成員屬性時就是由這兩個函數完成的。 __(1)讀取屬性:__ 通過對象或方法內通過$this訪問屬性,比如:`echo $obj->name;`,具體的實現: ```c zval *zend_std_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv) { zend_object *zobj; uint32_t property_offset; zobj = Z_OBJ_P(object); //根據屬性名在zend_class.zend_property_info中查找zend_property_info,得到屬性值在zend_object中的存儲offset //注意:zend_get_property_offset()會對屬性的可見性(public、private、protected)進行驗證 property_offset = zend_get_property_offset(zobj->ce, Z_STR_P(member), (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot); if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) { if (EXPECTED(property_offset != ZEND_DYNAMIC_PROPERTY_OFFSET)) { //普通屬性,直接根據offset取到屬性值:((zval*)((char*)(zobj) + offset)) retval = OBJ_PROP(zobj, property_offset); } else if (EXPECTED(zobj->properties != NULL)) { //動態屬性的情況,沒有在類中顯式定義的屬性,后面一節會單獨介紹 .... } } else if (UNEXPECTED(EG(exception))) { ... } //沒有找到屬性 //調用魔術方法:__isset() if ((type == BP_VAR_IS) && zobj->ce->__isset) { ... } //調用魔術方法:__get() if (zobj->ce->__get) { zend_long *guard = zend_get_property_guard(zobj, Z_STR_P(member)); ... if(!((*guard) & IN_ISSET)){ *guard |= IN_ISSET; zend_std_call_issetter(&tmp_object, member, &tmp_result); *guard &= ~IN_ISSET; ... } } ... } ``` 普通成員屬性的查找比較容易理解,首先是從zend_class的屬性信息哈希表中找到zend_property_info,并判斷其可見性(public、private、protected),如果可以訪問則直接根據屬性的offset在zend_object.properties_table數組中取到屬性值,如果沒有在屬性哈希表中找到且定義了__get()魔術方法則會調用__get()方法處理。 > __Note:__ 如果類存在__get()方法,則在實例化對象分配屬性內存(即:properties_table)時會多分配一個zval,類型為HashTable,每次調用__get($var)時會把輸入的$var名稱存入這個哈希表,這樣做的目的是防止循環調用,舉個例子: > > ***public function __get($var) { return $this->$var; }*** > > 這種情況是調用__get()時又訪問了一個不存在的屬性,也就是會在__get()方法中遞歸調用,如果不對請求的$var作判斷則將一直遞歸下去,所以在調用__get()前首先會判斷當前$var是不是已經在__get()中了,如果是則不會再調用__get(),否則會把$var作為key插入那個HashTable,然后將哈希值設置為:*guard |= IN_ISSET,調用完__get()再把哈希值設置為:*guard &= ~IN_ISSET。 > > 這個HashTable不僅僅是給__get()用的,其它魔術方法也會用到,所以其哈希值類型是zend_long,不同的魔術方法占不同的bit位;其次,并不是所有的對象都會額外分配這個HashTable,在對象創建時會根據 ***zend_class_entry.ce_flags*** 是否包含 ***ZEND_ACC_USE_GUARDS*** 確定是否分配,在類編譯時如果發現定義了__get()、__set()、__unset()、__isset()方法則會將ce_flags打上這個掩碼。 __(2)設置屬性:__ 與讀取屬性不同,設置屬性是對屬性的修改操作,比如:`$obj->name = "pangudashu";`,看下具體的實現過程: ```c ZEND_API void zend_std_write_property(zval *object, zval *member, zval *value, void **cache_slot) { zend_object *zobj; uint32_t property_offset; zobj = Z_OBJ_P(object); //與讀取屬性相同 property_offset = zend_get_property_offset(zobj->ce, Z_STR_P(member), (zobj->ce->__set != NULL), cache_slot); if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) { if (EXPECTED(property_offset != ZEND_DYNAMIC_PROPERTY_OFFSET)) { //普通屬性 variable_ptr = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(variable_ptr) != IS_UNDEF) { goto found; } } else if (EXPECTED(zobj->properties != NULL)) { //動態屬性哈希表已經初始化,直接插入zobj->properties哈希表,后面單獨介紹 ... if ((variable_ptr = zend_hash_find(zobj->properties, Z_STR_P(member))) != NULL) { found: //賦值操作,與普通變量的操作相同 zend_assign_to_variable(variable_ptr, value, IS_CV); goto exit; } } } else if (UNEXPECTED(EG(exception))) { ... } //沒有找到屬性 //如果定義了__set()則調用 if (zobj->ce->__set) { //與__get()相同,也會判斷set的變量名是否已經在__set()中 ... ZVAL_COPY(&tmp_object, object); (*guard) |= IN_SET; //防止循環__set() if (zend_std_call_setter(&tmp_object, member, value) != SUCCESS) { } (*guard) &= ~IN_SET; }else if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) { ... } } ``` 首先與讀取屬性的操作相同:先找到zend_property_info,判斷其可見性,然后根據offset取到具體的屬性值,最后對其進行賦值修改。 > __Note:__ 屬性讀寫操作的函數中有一個cache_slot的參數,它的作用涉及PHP的一個緩存機制:運行時緩存,后面會單獨介紹。 #### 3.4.2.4 對象的復制 PHP中普通變量的復制可以通過直接賦值完成,比如: ```php $a = array(); $b = $a; ``` 但是對象無法這么進行復制,僅僅通過賦值傳遞對象,它們指向的都是同一個對象,修改時也不會發生硬拷貝。比如上面這個例子,我們把`$a`賦值給`$b`,然后如果我們修改`$b`的內容,那么這時候會進行value分離,`$a`的內容是不變的,但是如果是把一個對象賦值給了另一個變量,這倆對象不管哪一個修改另外一個都隨之改變。 ```php class my_class { public $arr = array(); } $a = new my_class; $b = $a; $b->arr[] = 1; var_dump($a === $b); ==================== 輸出:bool(true) ``` 還記得我們在《2.1.3.2 寫時復制》一節講過zval有個類型掩碼: __type_flag__ 嗎?其中有個是否可復制的標識:__IS_TYPE_COPYABLE__ ,copyable的意思是當value發生duplication時是否需要或能夠copy,而object的類型是不能復制(不清楚的可以翻下前面的章節),所以我們不能簡單的通過賦值語句進行對象的復制。 PHP提供了另外一個關鍵詞來實現對象的復制:__clone__。 ```php $copy_of_object = clone $object; ``` `clone`出的對象就與原來的對象完全隔離了,各自修改都不會相互影響,另外如果類中定義了`__clone()`魔術方法,那么在`clone`時將調用此函數。 `clone`的實現比較簡單,通過`zend_object.clone_obj`(即:`zend_objects_clone_obj()`)完成。 ```c //zend_objects.c ZEND_API zend_object *zend_objects_clone_obj(zval *zobject) { zend_object *old_object; zend_object *new_object; old_object = Z_OBJ_P(zobject); //重新分配一個zend_object new_object = zend_objects_new(old_object->ce); //淺復制properties_table、properties //如果定義了__clone()則調用此方法 zend_objects_clone_members(new_object, old_object); return new_object; } ``` #### 3.4.2.5 對象比較 當使用比較運算符(==)比較兩個對象變量時,比較的原則是:如果兩個對象的屬性和屬性值 都相等,而且兩個對象是同一個類的實例,那么這兩個對象變量相等;而如果使用全等運算符(===),這兩個對象變量一定要指向某個類的同一個實例(即同一個對象)。 PHP中對象間的"=="比較通過函數`zend_std_compare_objects()`處理。 ```c static int zend_std_compare_objects(zval *o1, zval *o2) { ... if (zobj1->ce != zobj2->ce) { return 1; /* different classes */ } if (!zobj1->properties && !zobj2->properties) { //逐個比較properties_table ... }else{ //比較properties return zend_compare_symbol_tables(zobj1->properties, zobj2->properties); } } ``` "==="的比較通過函數`zend_is_identical()`處理,比較簡單,這里不再展開。 #### 3.4.2.6 對象的銷毀 object與string、array等類型不同,它是個復合類型,所以它的銷毀過程更加復雜,賦值、函數調用結束或主動unset等操作中如果發現object引用計數為0則將觸發銷毀動作。 ```php //情況1 $obj1 = new my_function(); $obj1 = 123; //此時將斷開對zend_object的引用,如果refcount=0則銷毀 //情況2 function xxxx(){ $obj1 = new my_function(); ... return null; //清理局部變量時如果發現$obj1引用為0則銷毀 } //情況3 $obj1 = new my_function(); //整個腳本結束,清理全局變量時 //情況4 $obj1 = new my_function(); unset($obj1); ``` 上面這幾個都是比較常見的會進行變量銷毀的情況,銷毀一個對象由`zend_objects_store_del()`完成,銷毀的過程主要是清理成員屬性、從EG(objects_store).object_buckets中刪除、釋放zend_object內存等等。 ```c //zend_objects_API.c ZEND_API void zend_objects_store_del(zend_object *object) { //這個函數if嵌套寫的很挫... ... if (GC_REFCOUNT(object) > 0) { GC_REFCOUNT(object)--; return; } ... //調用dtor_obj,默認zend_objects_destroy_object() //接著調用free_obj,默認zend_object_std_dtor() object->handlers->dtor_obj(object); object->handlers->free_obj(object); ... ptr = ((char*)object) - object->handlers->offset; efree(ptr); } ``` 另外,在減少refcount時如果發現object的引用計數大于0那么并不是什么都不做了,還記得2.1.3.4介紹的垃圾回收嗎?PHP變量類型有的會因為循環引用導致正常的gc無法生效,這種類型的變量就有可能成為垃圾,所以會對這些類型的`zval.u1.type_flag`打上`IS_TYPE_COLLECTABLE`標簽,然后在減少引用時即使refcount大于0也會啟動垃圾檢查,目前只有object、array兩種類型會使用這種機制。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看