您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“PHP 7和PHP 5中的對象之間的差異”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
?? PHP 中的 class、interface、trait 在底層均以 zend_class_entry 結(jié)構(gòu)體實現(xiàn)
struct _zend_class_entry { char type; const char *name; zend_uint name_length; struct _zend_class_entry *parent; int refcount; zend_uint ce_flags; HashTable function_table; HashTable properties_info; zval **default_properties_table; zval **default_static_members_table; zval **static_members_table; HashTable constants_table; int default_properties_count; int default_static_members_count; 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 *serialize_func; union _zend_function *unserialize_func; zend_class_iterator_funcs iterator_funcs; /* handlers */ zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC); zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC); int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* a class implements this interface */ union _zend_function *(*get_static_method)(zend_class_entry *ce, char* method, int method_len TSRMLS_DC); /* serializer callbacks */ int (*serialize)(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC); int (*unserialize)(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC); zend_class_entry **interfaces; zend_uint num_interfaces; zend_class_entry **traits; zend_uint num_traits; zend_trait_alias **trait_aliases; zend_trait_precedence **trait_precedences; union { struct { const char *filename; zend_uint line_start; zend_uint line_end; const char *doc_comment; zend_uint doc_comment_len; } user; struct { const struct _zend_function_entry *builtin_functions; struct _zend_module_entry *module; } internal; } info; };
??zend_class_entry 結(jié)構(gòu)體中包含大量的指針以及 hashtable,這就導(dǎo)致結(jié)構(gòu)體本身會占用不小的內(nèi)存空間。另外,結(jié)構(gòu)體中的指針還需要單獨分配相應(yīng)的內(nèi)存空間,這又會消耗一部分內(nèi)存空間。
??所謂開發(fā)者自定義的 class 即使用 PHP 語言定義的 class,而 PHP 內(nèi)部定義的 class 是指 PHP 源代碼中定義的 class 或 PHP 擴展中定義的 class。二者最本質(zhì)的區(qū)別在于生命周期不同:
以 php-fpm 為例,當(dāng)請求到來時,PHP 會解析開發(fā)者定義的 class 并為其分配相應(yīng)的內(nèi)存空間。其后在處理請求的過程中,PHP 會對這些 class 進(jìn)行相應(yīng)的調(diào)用,最后在處理完請求之后銷毀這些 class,釋放之前為其分配的內(nèi)存空間。
為了節(jié)約內(nèi)存空間,不要在代碼中定義一些實際并不使用的 class??梢允褂?autoload 來屏蔽這些實際并不使用的 class,因為 autoload 只有在一個 class 被用到時才加載和解析,但這樣就會把 class 的解析和加載過程由代碼的編譯階段延后到代碼的執(zhí)行階段,影響性能
另外需要注意的是,即使開啟了 OPCache 擴展,開發(fā)者自定義的 class 還是會隨著請求的到來而解析和加載,隨著請求的完成而銷毀,OPCache 只是提高了這兩個階段的速度
PHP 內(nèi)部定義的 class 則不同。仍然以 php-fpm 為例,當(dāng)一個 php-fpm 進(jìn)程啟動時,PHP 會為這些 class 一次性永久分配內(nèi)存空間,直到此 php-fpm 進(jìn)程消亡(為避免內(nèi)存泄漏,php-fpm 會在處理完一定數(shù)量的請求之后銷毀然后重啟)
if (EG(full_tables_cleanup)) { zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC); zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class_full TSRMLS_CC); } else { zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function TSRMLS_CC); zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class TSRMLS_CC); } static int clean_non_persistent_class(zend_class_entry **ce TSRMLS_DC) { return ((*ce)->type == ZEND_INTERNAL_CLASS) ? ZEND_HASH_APPLY_STOP : ZEND_HASH_APPLY_REMOVE; }
??由以上代碼可以看出,在請求結(jié)束時,PHP 內(nèi)部定義的 class 并不會被銷毀。另外,由于 PHP 擴展中定義的 class 也屬于 PHP 內(nèi)部定義的 class 的范疇,所以,從節(jié)省內(nèi)存空間的角度出發(fā),不要開啟一些自己并不使用的擴展。因為,如果擴展一旦開啟,擴展中定義的 class 就會在 php-fpm 進(jìn)程啟動時被解析和加載。
很多時候,為了處理方便,我們會通過繼承 \Exception 來自定義 exception。但由于 zend_class_entry 結(jié)構(gòu)體非常龐大,這就導(dǎo)致在提高便利的同時耗費了大量的內(nèi)存
?? class 綁定指的是 class 數(shù)據(jù)的準(zhǔn)備過程
??對于 PHP 內(nèi)部定義的 class,綁定過程在 class 注冊時就已經(jīng)完成。此過程發(fā)生在 PHP 腳本運行之前,并且在整個 php-fpm 進(jìn)程的生命周期中只發(fā)生一次。
??對于既沒有繼承 parent class,也沒有實現(xiàn) interface,也沒有使用 trait 的 class,綁定過程發(fā)生在 PHP 代碼的編輯階段,并且不會消耗太多資源。此種 class 的綁定通常只需要將 class 注冊到 class_table 中,并檢查 class 是否包含了抽象方法但沒有被申明為 abstract 類型。
void zend_do_early_binding(TSRMLS_D) /* {{{ */ { zend_op *opline = &CG(active_op_array)->opcodes[CG(active_op_array)->last-1]; HashTable *table; while (opline->opcode == ZEND_TICKS && opline > CG(active_op_array)->opcodes) { opline--; } switch (opline->opcode) { case ZEND_DECLARE_FUNCTION: if (do_bind_function(CG(active_op_array), opline, CG(function_table), 1) == FAILURE) { return; } table = CG(function_table); break; case ZEND_DECLARE_CLASS: if (do_bind_class(CG(active_op_array), opline, CG(class_table), 1 TSRMLS_CC) == NULL) { return; } table = CG(class_table); break; case ZEND_DECLARE_INHERITED_CLASS: { /*... ...*/ } case ZEND_VERIFY_ABSTRACT_CLASS: case ZEND_ADD_INTERFACE: case ZEND_ADD_TRAIT: case ZEND_BIND_TRAITS: /* We currently don't early-bind classes that implement interfaces */ /* Classes with traits are handled exactly the same, no early-bind here */ return; default: zend_error(E_COMPILE_ERROR, "Invalid binding type"); return; } /*... ...*/ } void zend_verify_abstract_class(zend_class_entry *ce TSRMLS_DC) { zend_abstract_info ai; if ((ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) && !(ce->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) { memset(&ai, 0, sizeof(ai)); zend_hash_apply_with_argument(&ce->function_table, (apply_func_arg_t) zend_verify_abstract_class_function, &ai TSRMLS_CC); if (ai.cnt) { zend_error(E_ERROR, "Class %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")", ce->name, ai.cnt, ai.cnt > 1 ? "s" : "", DISPLAY_ABSTRACT_FN(0), DISPLAY_ABSTRACT_FN(1), DISPLAY_ABSTRACT_FN(2) ); } } }
??對于實現(xiàn)了 interface 的 class 的綁定過程非常復(fù)雜,大致流程如下:
檢查 interface 是否已經(jīng)實現(xiàn)
檢查實現(xiàn)該 interface 的確實是一個 class,而不是 interface 自身(class、interface、trait 的底層數(shù)據(jù)結(jié)構(gòu)都是 zend_class_entry)
復(fù)制常量,并檢查可能存在的沖突
復(fù)制方法,并檢查可能存在的沖突,除此之外還需要檢查訪問控制
將 interface 加入到 zend_class_entry 的 **interfaces
中
需要注意的是,所謂的復(fù)制只是將常量、屬性、方法的引用計數(shù)加 1
ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry *iface TSRMLS_DC) { /* ... ... */ } else { if (ce->num_interfaces >= current_iface_num) { /* resize the vector if needed */ if (ce->type == ZEND_INTERNAL_CLASS) { /*對于內(nèi)部定義的 class,使用 realloc 分配內(nèi)存,所分配的內(nèi)存在進(jìn)程的生命周期中永久有效*/ ce->interfaces = (zend_class_entry **) realloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num)); } else { /*對于開發(fā)者定義的 class,使用 erealloc 分配內(nèi)存,所分配的內(nèi)存只在請求的生命周期中有效*/ ce->interfaces = (zend_class_entry **) erealloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num)); } } ce->interfaces[ce->num_interfaces++] = iface; /* Add the interface to the class */ /* Copy every constants from the interface constants table to the current class constants table */ zend_hash_merge_ex(&ce->constants_table, &iface->constants_table, (copy_ctor_func_t) zval_add_ref, sizeof(zval *), (merge_checker_func_t) do_inherit_constant_check, iface); /* Copy every methods from the interface methods table to the current class methods table */ zend_hash_merge_ex(&ce->function_table, &iface->function_table, (copy_ctor_func_t) do_inherit_method, sizeof(zend_function), (merge_checker_func_t) do_inherit_method_check, ce); do_implement_interface(ce, iface TSRMLS_CC); zend_do_inherit_interfaces(ce, iface TSRMLS_CC); } }
??對于常量的復(fù)制,zval_add_ref 用于將常量的引用計數(shù)加1;而對于方法的復(fù)制,do_inherit_method 除了將相應(yīng)方法的引用計數(shù)加 1 之外,還將方法中定義的靜態(tài)變量的引用計數(shù)加 1。
static void do_inherit_method(zend_function *function) { function_add_ref(function); } ZEND_API void function_add_ref(zend_function *function) { if (function->type == ZEND_USER_FUNCTION) { zend_op_array *op_array = &function->op_array; (*op_array->refcount)++; if (op_array->static_variables) { HashTable *static_variables = op_array->static_variables; zval *tmp_zval; ALLOC_HASHTABLE(op_array->static_variables); zend_hash_init(op_array->static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0); zend_hash_copy(op_array->static_variables, static_variables, (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *)); } op_array->run_time_cache = NULL; } }
??對于實現(xiàn)了 interface 的 class 的綁定,由于要進(jìn)行多次的循環(huán)遍歷以及檢查,通常非常消耗 CPU 資源,但卻節(jié)省了內(nèi)存空間。
現(xiàn)階段,PHP 將 interface 的綁定推遲到了代碼執(zhí)行階段進(jìn)行,以為這每次請求都會進(jìn)行這些操作
??對于 class 繼承的綁定,過程與 interface 的綁定類似,但更為復(fù)雜。另外有一個值得注意的地方,如果 class 在綁定時已經(jīng)解析到了父類,則綁定發(fā)生在代碼編譯階段;否則發(fā)生在代碼執(zhí)行階段。
// A 在 B 之前申明,B 的綁定發(fā)生在編譯階段 class A { } class B extends A { } // A 在 B 之后申明,綁定 B 時編譯器無法知道 A 情況,此時 B 的綁定只能延后到代碼執(zhí)行時 class B extends A { } class A { } // 這種情況會報錯:Class B doesn't exist // 在代碼執(zhí)行階段綁定 C,需要解析 B,但此時 B 有繼承了 A,而 A 此時還是未知狀態(tài) class C extends B { } class B extends A { } class A { }
如果使用 autoload,并且采用一個 class 對應(yīng)一個文件的模式,則所有 class 的綁定都只會發(fā)生在代碼執(zhí)行階段
??方法與函數(shù)的底層數(shù)據(jù)結(jié)構(gòu)均為 zend_function。PHP 編譯器在編譯時將方法編譯并添加到 zend_class_entry 的 function_table 屬性中。所以,在 PHP 代碼運行時,方法已經(jīng)編譯完成,PHP 要做的只是通過指針找到方法并執(zhí)行。
typedef union _zend_function { zend_uchar type; struct { zend_uchar type; const char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; } common; zend_op_array op_array; zend_internal_function internal_function; } zend_function;
??當(dāng) object 嘗試調(diào)用方法時,首先會在其對應(yīng)的 class 的 function_table 中查找該方法,同時還會檢查方法的訪問控制。如果方法不存在或方法的訪問控制不符合要求,object 會嘗試調(diào)用莫屬方法 __call
。
static inline union _zend_function *zend_get_user_call_function(zend_class_entry *ce, const char *method_name, int method_len) { zend_internal_function *call_user_call = emalloc(sizeof(zend_internal_function)); call_user_call->type = ZEND_INTERNAL_FUNCTION; call_user_call->module = (ce->type == ZEND_INTERNAL_CLASS) ? ce->info.internal.module : NULL; call_user_call->handler = zend_std_call_user_call; call_user_call->arg_info = NULL; call_user_call->num_args = 0; call_user_call->scope = ce; call_user_call->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; call_user_call->function_name = estrndup(method_name, method_len); return (union _zend_function *)call_user_call; } static union _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int method_len, const zend_literal *key TSRMLS_DC) { zend_function *fbc; zval *object = *object_ptr; zend_object *zobj = Z_OBJ_P(object); ulong hash_value; char *lc_method_name; ALLOCA_FLAG(use_heap) if (EXPECTED(key != NULL)) { lc_method_name = Z_STRVAL(key->constant); hash_value = key->hash_value; } else { lc_method_name = do_alloca(method_len+1, use_heap); /* Create a zend_copy_str_tolower(dest, src, src_length); */ zend_str_tolower_copy(lc_method_name, method_name, method_len); hash_value = zend_hash_func(lc_method_name, method_len+1); } if (UNEXPECTED(zend_hash_quick_find(&zobj->ce->function_table, lc_method_name, method_len+1, hash_value, (void **)&fbc) == FAILURE)) { if (UNEXPECTED(!key)) { free_alloca(lc_method_name, use_heap); } if (zobj->ce->__call) { return zend_get_user_call_function(zobj->ce, method_name, method_len); } else { return NULL; } } /* Check access level */ if (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) { zend_function *updated_fbc; /* Ensure that if we're calling a private function, we're allowed to do so. * If we're not and __call() handler exists, invoke it, otherwise error out. */ updated_fbc = zend_check_private_int(fbc, Z_OBJ_HANDLER_P(object, get_class_entry)(object TSRMLS_CC), lc_method_name, method_len, hash_value TSRMLS_CC); if (EXPECTED(updated_fbc != NULL)) { fbc = updated_fbc; } else { if (zobj->ce->__call) { fbc = zend_get_user_call_function(zobj->ce, method_name, method_len); } else { zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : ""); } } } else { /* Ensure that we haven't overridden a private function and end up calling * the overriding public function... */ if (EG(scope) && is_derived_class(fbc->common.scope, EG(scope)) && fbc->op_array.fn_flags & ZEND_ACC_CHANGED) { zend_function *priv_fbc; if (zend_hash_quick_find(&EG(scope)->function_table, lc_method_name, method_len+1, hash_value, (void **) &priv_fbc)==SUCCESS && priv_fbc->common.fn_flags & ZEND_ACC_PRIVATE && priv_fbc->common.scope == EG(scope)) { fbc = priv_fbc; } } if ((fbc->common.fn_flags & ZEND_ACC_PROTECTED)) { /* Ensure that if we're calling a protected function, we're allowed to do so. * If we're not and __call() handler exists, invoke it, otherwise error out. */ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fbc), EG(scope)))) { if (zobj->ce->__call) { fbc = zend_get_user_call_function(zobj->ce, method_name, method_len); } else { zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : ""); } } } } if (UNEXPECTED(!key)) { free_alloca(lc_method_name, use_heap); } return fbc; }
??這里需要指出的是:
由于 PHP 對大小寫不敏感,所以所有的方法名稱都會被轉(zhuǎn)為小寫(zend_str_tolower_copy())
為了避免不必要的資源消耗,PHP 5.4 開始引入了 zend_literal 結(jié)構(gòu)體,即參數(shù) key
typedef struct _zend_literal { zval constant; zend_ulong hash_value; zend_uint cache_slot; } zend_literal;
??其中,constant 記錄了轉(zhuǎn)為小寫后的字符串,hash_value 則是預(yù)先計算好的 hash。這樣就避免了 object 每次調(diào)用方法都要將方法名稱轉(zhuǎn)為小寫并計算 hash 值。
class Foo { public function BAR() { } } $a = new Foo; $b = 'bar'; $a->bar(); /* good */ $a->$b(); /* bad */
??在上例中,在代碼編譯階段,方法 BAR 被轉(zhuǎn)換成 bar 并添加到 zend_class_entry 的 function_table 中。當(dāng)發(fā)生方法調(diào)用時:
第一種情形,在代碼編譯階段,方法名稱 bar 確定為字符串常量,編譯器可以預(yù)先計算好其對應(yīng)的 zend_literal 結(jié)構(gòu),即 key 參數(shù)。這樣,代碼在執(zhí)行時相對會更快。
第二種情形,由于在編譯階段編譯器對 $b 一無所知,這就需要在代碼執(zhí)行階段現(xiàn)將方法名稱轉(zhuǎn)為小寫,然后計算 hash 值。
??當(dāng)對一個 class 進(jìn)行實例化時,object 中的屬性只是對 class 中屬性的引用。這樣,object 的創(chuàng)建操作就會相對輕量化,并且會節(jié)省一部分內(nèi)存空間。
??如果要對 object 中的屬性進(jìn)行修改,zend 引擎會單獨創(chuàng)建一個 zval 結(jié)構(gòu),只對當(dāng)前 object 的當(dāng)前屬性產(chǎn)生影響。
??class 的實例化對應(yīng)的會在底層創(chuàng)建一個 zend_obejct 數(shù)據(jù)結(jié)構(gòu),新創(chuàng)建的 object 會注冊到 zend_objects_store 中。zend_objects_store 是一個全局的 object 注冊表,同一個對象在該注冊表中只能注冊一次。
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; /* protects from __get/__set ... recursion */ } zend_object; typedef struct _zend_objects_store {/*本質(zhì)上是一個動態(tài) object_bucket 數(shù)組*/ zend_object_store_bucket *object_buckets; zend_uint top; /*下一個可用的 handle,handle 取值從 1 開始。對應(yīng)的在 *object_buckets 中的 index 為 handle - 1*/ zend_uint size; /*當(dāng)前分配的 *object_buckets 的最大長度*/ int free_list_head; /*當(dāng) *object_bucket 中的 bucket 被銷毀后,該 bucket 在 *object_buckets 中的 index 會被有序加入 free_list 鏈表。free_list_head 即為該鏈表中的第一個值*/ } zend_objects_store; typedef struct _zend_object_store_bucket { zend_bool destructor_called; zend_bool valid; /*值為 1 表示當(dāng)前 bucket 被使用,此時 store_bucket 中的 store_object 被使用;值為 0 表示當(dāng)前 bucket 并沒有存儲有效的 object,此時 store_bucket 中的 free_list 被使用*/ zend_uchar apply_count; union _store_bucket { struct _store_object { void *object; zend_objects_store_dtor_t dtor; zend_objects_free_object_storage_t free_storage; zend_objects_store_clone_t clone; const zend_object_handlers *handlers; zend_uint refcount; gc_root_buffer *buffered; } obj; struct { int next; /*第一個未被使用的 bucket 的 index 永遠(yuǎn)存儲在 zend_object_store 的 free_list_head 中,所以 next 只需要記錄當(dāng)前 bucket 之后第一個未被使用的 bucket 的 index*/ } free_list; } bucket; } zend_object_store_bucket; ZEND_API zend_object_value zend_objects_new(zend_object **object, zend_class_entry *class_type TSRMLS_DC) { zend_object_value retval; *object = emalloc(sizeof(zend_object)); (*object)->ce = class_type; (*object)->properties = NULL; (*object)->properties_table = NULL; (*object)->guards = NULL; retval.handle = zend_objects_store_put(*object, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC); retval.handlers = &std_object_handlers; return retval; }
?? 將 object 注冊到 zend_objects_store 中以后,將會為 object 創(chuàng)建屬性(對相應(yīng) class 屬性的引用)
ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type) { int i; if (class_type->default_properties_count) { object->properties_table = emalloc(sizeof(zval*) * class_type->default_properties_count); for (i = 0; i < class_type->default_properties_count; i++) { object->properties_table[i] = class_type->default_properties_table[i]; if (class_type->default_properties_table[i]) { #if ZTS ALLOC_ZVAL( object->properties_table[i]); MAKE_COPY_ZVAL(&class_type->default_properties_table[i], object->properties_table[i]); #else Z_ADDREF_P(object->properties_table[i]); #endif } } object->properties = NULL; } }
??需要指出的是,在創(chuàng)建屬性時,如果是非線程安全模式的 PHP,僅僅是增加相應(yīng)屬性的引用計數(shù);但如果是線程安全模式的 PHP,則需要對屬性進(jìn)行深度復(fù)制,將 class 的屬性全部復(fù)制到 object 中的 properties_table 中。
這也說明,線程安全的 PHP 比非線程安全的 PHP 運行慢,并且更耗費內(nèi)存
每個屬性在底層都對應(yīng)一個 zend_property_info 結(jié)構(gòu):
typedef struct _zend_property_info { zend_uint flags; const char *name; int name_length; ulong h; int offset; const char *doc_comment; int doc_comment_len; zend_class_entry *ce; } zend_property_info;
??class 中聲明的每個屬性,在 zend_class_entry 中的 properties_table 中都有一個zend_property_info 與之相對應(yīng)。properties_table 可以幫助我們快速確定一個 object 所訪問的屬性是否存在:
如果屬性不存在,并且我們嘗試向 object 寫入該屬性:如果 class 定義了 __set
方法,則使用 __set
方法寫入該屬性;否則會向 object 添加一個動態(tài)屬性。但無論以何種方式寫入該屬性,寫入的屬性都將添加到 object 的 properties_table 中。
如果屬性存在,則需要檢查相應(yīng)的訪問控制;對于 protected 和 private 類型,則需要檢查當(dāng)前的作用域。
在創(chuàng)建完 object 之后,只要我們不向 object 中寫入新的屬性或更新 object 對應(yīng)的 class 中的屬性的值,則 object 所占用的內(nèi)存空間不會發(fā)生變化。
屬性的存儲/訪問方式:
zend_class_entry->properties_info 中存儲的是一個個的 zend_property_info。而屬性的值實際以 zval 指針數(shù)組的方式存儲在 zend_class_entry->default_properties_table 中。object 中動態(tài)添加的屬性只會以 property_name => property_value 的形式存儲在 zend_object->properties_table 中。而在創(chuàng)建 object 時,zend_class_entry->properties_table 中的值會被逐個傳遞給 zend_object->properties_table。
zend_literal->cache_slot 中存儲的 int 值為 run_time_cache 中的索引 index。run_time_cache 為數(shù)組結(jié)構(gòu),index 對應(yīng)的 value 為訪問該屬性的 object 對應(yīng)的 zend_class_entry;index + 1 對應(yīng)的 value 為該屬性對應(yīng)的 zend_property_info 。在訪問屬性時,如果 zend_literal->cache_slot 中的值不為空,則可以通過 zend_literal->cache_slot 快速檢索得到 zend_property_info 結(jié)構(gòu);如果為空,則在檢索到 zend_property_info 的信息之后會初始化 zend_literal->cache_slot。
屬性名稱的存儲方式
private 屬性:"\0class_name\0property_name"
protected 屬性:"\0*\0property_name"
public 屬性:"property_name"
?? 執(zhí)行以下代碼,看看輸出結(jié)果
class A { private $a = 'a'; protected $b = 'b'; public $c = 'c'; } class B extends A { private $a = 'aa'; protected $b = 'bb'; public $c = 'cc'; } class C extends B { private $a = 'aaa'; protected $b = 'bbb'; public $c = 'ccc'; } var_dump(new C());
zend_object 中 guards 的作用
guards 的作用是對 object 的重載提供遞歸保護(hù)。
class Foo { public function __set($name, $value) { $this->$name = $value; } } $foo = new Foo; $foo->bar = 'baz'; var_dump($foo->bar);
?? 以上代碼中,當(dāng)為 bar 屬性時會調(diào)用 __set
方法。但 $bar 屬性在 Foo 中并不存在,按照常理,此時又會遞歸調(diào)用 __set
方法。為了避免這種遞歸調(diào)用,PHP 會使用 zend_guard 來判斷當(dāng)前是否已經(jīng)處于重載方法的上下文中。
typedef struct _zend_guard { zend_bool in_get; zend_bool in_set; zend_bool in_unset; zend_bool in_isset; zend_bool dummy; /* sizeof(zend_guard) must not be equal to sizeof(void*) */ } zend_guard;
??首先需要申明:object 并不是引用傳遞。之所以會出現(xiàn) object 是引用傳遞的假象,原因在于我們傳遞給函數(shù)的參數(shù)中所存儲的只是 object 在 zend_objects_store 中的 ID(handle)。通過這個 ID,我們可以在 zend_objects_store 中查找并加載真正的 object,然后訪問并修改 object 中的屬性。
PHP 中,函數(shù)內(nèi)外是兩個不同的作用域,對于同一變量,在函數(shù)內(nèi)部對其修改不會影響到函數(shù)外部。但通過 object 的 ID(handle)訪問并修改 object 的屬性并不受此限制。
$a = 1; function test($a) { $a = 3; echo $a; // 輸出 3 } test($a); echo $a; // 輸出 1
同一個 object 在 zend_objects_store 中只存儲一次。要向 zend_objects_store 中寫入新的對象,只能通過 new 關(guān)鍵字、unserialize 函數(shù)、反射、clone 四種方式。
??$this
在使用時會自動接管當(dāng)前對象,PHP 禁止對 this 的賦值操作都會引起錯誤
static zend_bool opline_is_fetch_this(const zend_op *opline TSRMLS_DC) { if ((opline->opcode == ZEND_FETCH_W) && (opline->op1_type == IS_CONST) && (Z_TYPE(CONSTANT(opline->op1.constant)) == IS_STRING) && ((opline->extended_value & ZEND_FETCH_STATIC_MEMBER) != ZEND_FETCH_STATIC_MEMBER) && (Z_HASH_P(&CONSTANT(opline->op1.constant)) == THIS_HASHVAL) && (Z_STRLEN(CONSTANT(opline->op1.constant)) == (sizeof("this")-1)) && !memcmp(Z_STRVAL(CONSTANT(opline->op1.constant)), "this", sizeof("this"))) { return 1; } else { return 0; } } /* ... ... */ if (opline_is_fetch_this(last_op TSRMLS_CC)) { zend_error(E_COMPILE_ERROR, "Cannot re-assign $this"); } /* ... ... */
?? 在 PHP 中進(jìn)行方法調(diào)用時,對應(yīng)執(zhí)行的 OPCode 為 INIT_METHOD_CALL。以 $a->foo()
為例,在 INIT_METHOD_CALL 中,Zend 引擎知道是由 $a
發(fā)起的方法調(diào)用,所以 Zend 引擎會把 $a
的值存入全局空間。在實際執(zhí)行方法調(diào)用時,對應(yīng)執(zhí)行的 OPCode 為 DO_FCALL。在 DO_FCALL 中,Zend 引擎會將之前存入全局空間的 $a
賦值給 $this
的指針,即 EG(This):
if (fbc->type == ZEND_USER_FUNCTION || fbc->common.scope) { should_change_scope = 1; EX(current_this) = EG(This); EX(current_scope) = EG(scope); EX(current_called_scope) = EG(called_scope); EG(This) = EX(object); /* fetch the object prepared in previous INIT_METHOD opcode and affect it to EG(This) */ EG(scope) = (fbc->type == ZEND_USER_FUNCTION || !EX(object)) ? fbc->common.scope : NULL; EG(called_scope) = EX(call)->called_scope; }
?? 在實際執(zhí)行方法體中的代碼時,如果出現(xiàn)使用 $this
進(jìn)行方法調(diào)用或?qū)傩再x值的情況,如 $this->a = 8
對應(yīng)的將執(zhí)行 OPCode ZEND_ASSIGN_OBJ,此時將從 EG(This) 取得 $this 的值
static zend_always_inline zval **_get_obj_zval_ptr_ptr_unused(TSRMLS_D) { if (EXPECTED(EG(This) != NULL)) { return &EG(This); } else { zend_error_noreturn(E_ERROR, "Using $this when not in object context"); return NULL; } }
??Zend 引擎在構(gòu)建方法堆棧時,$this
會被存入符號表,就像其他的變量一樣。這樣,當(dāng)使用 $this
進(jìn)行方法調(diào)用或?qū)?$this
作為方法的參數(shù)時,Zend 引擎將從符號表中獲取 $this
。
if (op_array->this_var != -1 && EG(This)) { Z_ADDREF_P(EG(This)); /* For $this pointer */ if (!EG(active_symbol_table)) { EX_CV(op_array->this_var) = (zval **) EX_CV_NUM(execute_data, op_array->last_var + op_array->this_var); *EX_CV(op_array->this_var) = EG(This); } else { if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), (void **) EX_CV_NUM(execute_data, op_array->this_var))==FAILURE) { Z_DELREF_P(EG(This)); } } }
?? 最后是關(guān)于作用域的問題,當(dāng)進(jìn)行方法調(diào)用時,Zend 引擎會將作用域設(shè)置為 EG(scope)。EG(scope) 是 zend_class_entry 類型,也就是說,在方法中任何關(guān)于 object 的操作的作用域都是 object 對應(yīng)的 class。對屬性的訪問控制的檢查也是同樣:
ZEND_API int zend_check_protected(zend_class_entry *ce, zend_class_entry *scope) { zend_class_entry *fbc_scope = ce; /* Is the context that's calling the function, the same as one of * the function's parents? */ while (fbc_scope) { if (fbc_scope==scope) { return 1; } fbc_scope = fbc_scope->parent; } /* Is the function's scope the same as our current object context, * or any of the parents of our context? */ while (scope) { if (scope==ce) { return 1; } scope = scope->parent; } return 0; } static zend_always_inline int zend_verify_property_access(zend_property_info *property_info, zend_class_entry *ce TSRMLS_DC) { switch (property_info->flags & ZEND_ACC_PPP_MASK) { case ZEND_ACC_PUBLIC: return 1; case ZEND_ACC_PROTECTED: return zend_check_protected(property_info->ce, EG(scope)); case ZEND_ACC_PRIVATE: if ((ce==EG(scope) || property_info->ce == EG(scope)) && EG(scope)) { return 1; } else { return 0; } break; } return 0; }
??正是由于上述特性,所以以下代碼可以正常運行
class A { private $a; public function foo(A $obj) { $this->a = 'foo'; $obj->a = 'bar'; /* yes, this is possible */ } } $a = new A; $b = new A; $a->foo($b);
PHP 中 object 的作用域是 object 對應(yīng)的 class
??在 PHP 中,不要依賴 destruct 方法銷毀 object。因為當(dāng) PHP 發(fā)生致命錯誤時,destruct 方法并不會被調(diào)用。
ZEND_API void zend_hash_reverse_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC) { Bucket *p, *q; IS_CONSISTENT(ht); HASH_PROTECT_RECURSION(ht); p = ht->pListTail; while (p != NULL) { int result = apply_func(p->pData TSRMLS_CC); q = p; p = p->pListLast; if (result & ZEND_HASH_APPLY_REMOVE) { zend_hash_apply_deleter(ht, q); } if (result & ZEND_HASH_APPLY_STOP) { break; } } HASH_UNPROTECT_RECURSION(ht); } static int zval_call_destructor(zval **zv TSRMLS_DC) { if (Z_TYPE_PP(zv) == IS_OBJECT && Z_REFCOUNT_PP(zv) == 1) { return ZEND_HASH_APPLY_REMOVE; } else { return ZEND_HASH_APPLY_KEEP; } } void shutdown_destructors(TSRMLS_D) { zend_try { int symbols; do { symbols = zend_hash_num_elements(&EG(symbol_table)); zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor TSRMLS_CC); } while (symbols != zend_hash_num_elements(&EG(symbol_table))); zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC); } zend_catch { /* if we couldn't destruct cleanly, mark all objects as destructed anyway */ zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC); } zend_end_try(); }
??在調(diào)用 destruct 方法時,首先會從后往前遍歷整個符號表,調(diào)用所有引用計數(shù)為 1 的 object 的 destruct 方法;然后從前往后遍歷全局 object store,調(diào)用每個 object 的 destruct 方法。在此過程中如果有任何錯誤發(fā)生,就會停止調(diào)用 destruct 方法,然后將所有 object 的 destruct 方法都標(biāo)記為已調(diào)用過的狀態(tài)。
class Foo { public function __destruct() { var_dump("destroyed Foo"); } } class Bar { public function __destruct() { var_dump("destroyed Bar"); } } // 示例 1 $a = new Foo; $b = new Bar; "destroyed Bar" "destroyed Foo" // 示例 2 $a = new Bar; $b = new Foo; "destroyed Foo" "destroyed Bar" // 示例 3 $a = new Bar; $b = new Foo; $c = $b; /* $b 引用計數(shù)加 1 */ "destroyed Bar" "destroyed Foo" // 示例 4 class Foo { public function __destruct() { var_dump("destroyed Foo"); die();} } /* notice the die() here */ class Bar { public function __destruct() { var_dump("destroyed Bar"); } } $a = new Foo; $a2 = $a; $b = new Bar; $b2 = $b; "destroyed Foo"
?? 另外,不要在 destruct 方法中添加任何重要的代碼
class Foo { public function __destruct() { new Foo; } /* PHP 最終將崩潰 */ }
PHP 中對象的銷毀分為兩個階段:首先調(diào)用 destruct 方法(zend_object_store_bucket->bucket->obj->zend_objects_store_dtor_t),然后再釋放內(nèi)存(zend_object_store_bucket->bucket->obj->zend_objects_free_object_storage_t)。
之所以分為兩個階段執(zhí)行是因為 destruct 中執(zhí)行的是用戶級的代碼,即 PHP 代碼;而釋放內(nèi)存的代碼在系統(tǒng)底層運行。釋放內(nèi)存會破壞 PHP 的運行環(huán)境,為了使 destruct 中的 PHP 代碼能正常運行,所以分為兩個階段,這樣,保證在釋放內(nèi)存階段 object 已經(jīng)不被使用。
??與 PHP 5 相比,PHP 7 中的 object 在用戶層并沒有基本沒有什么變化;但在底層實現(xiàn)上,在內(nèi)存和性能方面做了一些優(yōu)化。
?? ① 首先,在 zval 中移除了之前的 zend_object_value 結(jié)構(gòu),直接嵌入了 zend_object。這樣,既節(jié)省了內(nèi)存空間,同時提高了通過 zval 查找 zend_object 的效率
/*PHP 7 中的 zend_object*/ struct _zend_object { zend_refcounted gc; uint32_t handle; zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1]; }; /*PHP 5 中的 zend_object_value*/ typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value;
?? 在 PHP 5 中通過 zval 訪問 object,先要通過 zva 中的 zend_object_value 找到 handle,然后通過handle 在 zend_object_store 中找到 zend_object_store_bucket,然后從 bucket 中解析出 object。在 PHP 7 中,zval 中直接存儲了 zend_object 的地址指針。
?? ② 其次,properties_table 利用了 struct hack 特性,這樣使得 zend_object 和 properties_table 存儲在一塊連續(xù)的內(nèi)存空間。同時,properties_table 中直接存儲了屬性的 zval 結(jié)構(gòu)。
?? ③ guards 不再出現(xiàn)在 zend_object 中。如果 class 中定義了魔術(shù)方法( __set
、__get
、__isset
、__unset
),則 guards 存儲在 properties_table 的第一個 slot 中;否則不存儲 guards。
?? ④ zend_object_store 及 zend_object_store_bucket 被移除,取而代之的是一個存儲各個 zend_object 指針的 C 數(shù)組,handle 為數(shù)組的索引。此外,之前 bucket 中存儲的 handlers 現(xiàn)在移入 zend_object 中;而之前 bucket 中的 dtor、free_storege、clone 現(xiàn)在則移入了 zend_object_handlers。
struct _zend_object_handlers { /* offset of real object header (usually zero) */ int offset; /* general object functions */ zend_object_free_obj_t free_obj; zend_object_dtor_obj_t dtor_obj; zend_object_clone_obj_t clone_obj; /* individual object functions */ // ... 其他與 PHP 5 相同 };
/*PHP 5 中的 custom_object*/ struct custom_object { zend_object std; my_custom_type *my_buffer; // ... }; /*PHP 7 中的 custom_object*/ struct custom_object { my_custom_type *my_buffer; // ... zend_object std; };
?? 由于 PHP 7 的 zend_object 中使用了 struct hack 特性來保證 zend_object 內(nèi)存的連續(xù),所以自定義 object 中的 zend_object 只能放在最后。而 zval 中存儲的只能是 zend_object,為了能通過 zend_object 順利解析出 custom_object ,在 zend_object 的 handlers 中記錄了 offset。
“PHP 7和PHP 5中的對象之間的差異”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。