Artikel ini akan membawa anda memahami objek dalam PHP 7 dan PHP 5, dan membandingkannya untuk melihat perbezaan antara objek tersebut!
1 Pengenalan kepada kelas
? kelas, antara muka dan sifat dalam PHP semuanya dilaksanakan oleh struktur zend_class_entry di lapisan bawah
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 Struktur mengandungi sejumlah besar penunjuk dan jadual hash, yang menyebabkan struktur itu sendiri menduduki sejumlah besar ruang memori. Di samping itu, penunjuk dalam struktur perlu memperuntukkan ruang memori yang sepadan secara berasingan, yang akan menggunakan beberapa ruang memori.
⒈ Perbandingan antara kelas yang ditentukan pembangun dan kelas yang ditentukan secara dalaman PHP
? Kelas yang ditakrifkan oleh pembangun ialah kelas yang ditakrifkan menggunakan bahasa PHP dan PHP yang ditakrifkan secara dalaman kelas merujuk kepada kelas yang ditakrifkan dalam kod sumber PHP atau kelas yang ditakrifkan dalam sambungan PHP. Perbezaan paling penting antara keduanya ialah kitaran hayat :
- Mengambil php-fpm sebagai contoh, apabila permintaan datang, PHP akan menghuraikan kelas yang ditentukan oleh pembangun dan berikannya ruang ingatan yang sepadan. Kemudian, semasa proses memproses permintaan, PHP akan membuat panggilan yang sepadan ke kelas ini Akhirnya, selepas memproses permintaan, kelas ini akan dimusnahkan dan ruang memori yang diperuntukkan untuk mereka akan dikeluarkan.
Untuk menjimatkan ruang memori, jangan tentukan beberapa kelas yang sebenarnya tidak digunakan dalam kod. Anda boleh menggunakan autoload untuk melindungi kelas ini yang sebenarnya tidak digunakan, kerana autoload hanya memuatkan dan menghuraikan kelas apabila ia digunakan, tetapi ini akan melambatkan proses penghuraian dan pemuatan kelas daripada fasa kompilasi kod kepada pelaksanaan peringkat kod, menjejaskan prestasi
Selain itu, perlu diingat bahawa walaupun sambungan OPCache dihidupkan, kelas yang ditentukan oleh pembangun masih akan dihuraikan dan dimuatkan dengan ketibaan permintaan dan dimusnahkan dengan penyempurnaan permintaan OPCache baru sahaja Meningkatkan kelajuan kedua-dua peringkat ini
- Kelas yang ditakrifkan secara dalaman dalam PHP adalah berbeza. Masih mengambil php-fpm sebagai contoh, apabila proses php-fpm dimulakan, PHP akan memperuntukkan ruang memori secara kekal untuk kelas ini sekali gus sehingga proses php-fpm mati (untuk mengelakkan kebocoran memori, php-fpm akan Musnahkan dan kemudian mulakan semula selepas bilangan permintaan)
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; }
? Seperti yang dapat dilihat daripada kod di atas, kelas yang ditakrifkan dalam PHP tidak akan dimusnahkan apabila permintaan itu tamat. Di samping itu, memandangkan kelas yang ditakrifkan dalam sambungan PHP juga tergolong dalam kategori kelas yang ditakrifkan dalam PHP, dari sudut penjimatan ruang memori, jangan buka beberapa sambungan yang anda tidak gunakan. Kerana, setelah sambungan didayakan, kelas yang ditakrifkan dalam sambungan akan dihuraikan dan dimuatkan apabila proses php-fpm bermula.
Banyak kali, untuk kemudahan, kami akan menyesuaikan pengecualian dengan mewarisi Exception. Walau bagaimanapun, oleh kerana struktur zend_class_entry adalah sangat besar, ia menggunakan banyak memori sambil meningkatkan kemudahan
⒉ class binding
? class binding pointer Ia adalah penyediaan proses data kelas
?Untuk kelas yang ditakrifkan secara dalaman dalam PHP, proses pengikatan selesai apabila kelas didaftarkan. Proses ini berlaku sebelum skrip PHP dijalankan, dan hanya berlaku sekali sepanjang hayat keseluruhan proses php-fpm.
??Untuk kelas yang tidak mewarisi kelas induk atau melaksanakan antara muka atau menggunakan ciri, proses pengikatan berlaku semasa fasa penyuntingan kod PHP dan tidak menggunakan terlalu banyak sumber. Pengikatan kelas jenis ini biasanya hanya memerlukan pendaftaran kelas ke dalam class_table dan menyemak sama ada kelas mengandungi kaedah abstrak tetapi tidak diisytiharkan sebagai jenis abstrak.
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) ); } } }
??Proses pengikatan untuk kelas yang melaksanakan antara muka adalah sangat rumit Proses umum adalah seperti berikut:
- Periksa sama ada antara muka telah dilaksanakan <. ??>Semak sama ada antara muka yang telah dilaksanakan sememangnya kelas, bukan antara muka itu sendiri (struktur data asas kelas, antara muka dan sifat semuanya zend_class_entry) Salin pemalar dan semak kemungkinan konflik Salin kaedah dan semak Kemungkinan konflik, selain menyemak kawalan akses Tambah antara muka pada
- zend_class_entry
**interfaces
Sepatutnya menyatakan bahawa salinan yang dipanggil Hanya tambah 1 pada kiraan rujukan pemalar, atribut dan kaedah
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)存在進程的生命周期中永久有效*/ 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); } }Untuk menyalin pemalar, zval_add_ref digunakan untuk menambah 1 pada kiraan rujukan pemalar ; untuk menyalin kaedah, do_inherit_method bukan sahaja menambah yang sepadan Selain menambah bilangan rujukan kaedah, kiraan rujukan pembolehubah statik yang ditakrifkan dalam kaedah juga ditambah.
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; } }?? Untuk pengikatan kelas yang melaksanakan antara muka, traversal dan semakan gelung berbilang biasanya diperlukan, yang biasanya menggunakan banyak sumber CPU, tetapi ia menjimatkan ruang memori.
Pada peringkat ini, PHP menangguhkan pengikatan antara muka ke peringkat pelaksanaan kod, memikirkan bahawa operasi ini akan dilakukan untuk setiap permintaan
??對于 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í)行階段
二、PHP 5 中的 object
⒈ object 中的方法
??方法與函數(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;
??當 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 中。當發(fā)生方法調(diào)用時:
- 第一種情形,在代碼編譯階段,方法名稱 bar 確定為字符串常量,編譯器可以預(yù)先計算好其對應(yīng)的 zend_literal 結(jié)構(gòu),即 key 參數(shù)。這樣,代碼在執(zhí)行時相對會更快。
- 第二種情形,由于在編譯階段編譯器對 $b 一無所知,這就需要在代碼執(zhí)行階段現(xiàn)將方法名稱轉(zhuǎn)為小寫,然后計算 hash 值。
⒉ object 中的屬性
??當對一個 class 進行實例化時,object 中的屬性只是對 class 中屬性的引用。這樣,object 的創(chuàng)建操作就會相對輕量化,并且會節(jié)省一部分內(nèi)存空間。
??如果要對 object 中的屬性進行修改,zend 引擎會單獨創(chuàng)建一個 zval 結(jié)構(gòu),只對當前 object 的當前屬性產(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; /*當前分配的 *object_buckets 的最大長度*/ int free_list_head; /*當 *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 表示當前 bucket 被使用,此時 store_bucket 中的 store_object 被使用;值為 0 表示當前 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 永遠存儲在 zend_object_store 的 free_list_head 中,所以 next 只需要記錄當前 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,則需要對屬性進行深度復(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 類型,則需要檢查當前的作用域。
在創(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 的重載提供遞歸保護。
class Foo { public function __set($name, $value) { $this->$name = $value; } } $foo = new Foo; $foo->bar = 'baz'; var_dump($foo->bar);
?? 以上代碼中,當為 bar 屬性時會調(diào)用 __set
方法。但 $bar 屬性在 Foo 中并不存在,按照常理,此時又會遞歸調(diào)用 __set
方法。為了避免這種遞歸調(diào)用,PHP 會使用 zend_guard 來判斷當前是否已經(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 的引用傳遞
??首先需要申明: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
??$this
在使用時會自動接管當前對象,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 中進行方法調(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
進行方法調(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
會被存入符號表,就像其他的變量一樣。這樣,當使用 $this
進行方法調(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)于作用域的問題,當進行方法調(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
⒌ 析構(gòu)方法 destruct
??在 PHP 中,不要依賴 destruct 方法銷毀 object。因為當 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 方法都標記為已調(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 7 中的 object
??與 PHP 5 相比,PHP 7 中的 object 在用戶層并沒有基本沒有什么變化;但在底層實現(xiàn)上,在內(nèi)存和性能方面做了一些優(yōu)化。
⒈ 在內(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 相同 };
⒉ 底層自定義 object 的變化(PHP 擴展中會用到自定義 object)
/*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。
推薦學(xué)習(xí):《PHP視頻教程》
Atas ialah kandungan terperinci Perbandingan ringkas objek dalam PHP 7 dan PHP 5. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undress AI Tool
Gambar buka pakaian secara percuma

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

JSON (JavaScriptObjectNotation) ialah format pertukaran data ringan yang telah menjadi format biasa untuk pertukaran data antara aplikasi web. Fungsi json_encode() PHP boleh menukar tatasusunan atau objek kepada rentetan JSON. Artikel ini akan memperkenalkan cara menggunakan fungsi json_encode() PHP, termasuk sintaks, parameter, nilai pulangan dan contoh khusus. Sintaks Sintaks fungsi json_encode() adalah seperti berikut: st

Gunakan fungsi __contains__() Python untuk mentakrifkan operasi pembendungan objek Python ialah bahasa pengaturcaraan ringkas dan berkuasa yang menyediakan banyak ciri berkuasa untuk mengendalikan pelbagai jenis data. Salah satunya adalah untuk melaksanakan operasi pembendungan objek dengan mentakrifkan fungsi __contains__(). Artikel ini akan memperkenalkan cara menggunakan fungsi __contains__() untuk mentakrifkan operasi pembendungan objek, dan memberikan beberapa kod sampel. Fungsi __contains__() ialah Pytho

Fungsi PHP boleh merangkum data ke dalam struktur tersuai dengan mengembalikan objek menggunakan pernyataan pulangan diikuti dengan contoh objek. Sintaks: functionget_object():object{}. Ini membolehkan mencipta objek dengan sifat dan kaedah tersuai dan memproses data dalam bentuk objek.

Begini cara untuk menukar tatasusunan hasil pertanyaan MySQL kepada objek: Cipta tatasusunan objek kosong. Gelung melalui tatasusunan yang terhasil dan buat objek baharu untuk setiap baris. Gunakan gelung foreach untuk menetapkan pasangan nilai kunci setiap baris kepada sifat yang sepadan bagi objek baharu. Menambah objek baharu pada tatasusunan objek. Tutup sambungan pangkalan data.

Dalam C++, terdapat tiga perkara yang perlu diperhatikan apabila fungsi mengembalikan objek: Kitaran hayat objek diuruskan oleh pemanggil untuk mengelakkan kebocoran memori. Elakkan penunjuk berjuntai dan pastikan objek kekal sah selepas fungsi kembali dengan memperuntukkan memori secara dinamik atau mengembalikan objek itu sendiri. Pengkompil boleh mengoptimumkan pembuatan salinan objek yang dikembalikan untuk meningkatkan prestasi, tetapi jika objek itu diluluskan oleh semantik nilai, tiada pembuatan salinan diperlukan.

Wedge Kita tahu bahawa objek dicipta dalam dua cara utama, satu adalah melalui Python/CAPI, dan satu lagi adalah dengan memanggil objek jenis. Contohnya objek jenis terbina dalam, kedua-dua kaedah disokong Contohnya, senarai boleh dibuat melalui [] atau list(). Tetapi sebagai contoh objek kelas tersuai, kita hanya boleh menciptanya dengan memanggil objek jenis. Jika objek boleh dipanggil, maka objek itu boleh dipanggil, jika tidak, ia tidak boleh dipanggil. Menentukan sama ada objek boleh dipanggil bergantung pada sama ada kaedah ditakrifkan dalam objek jenis sepadannya. suka

Tajuk: Menggunakan fungsi __le__() Python untuk menentukan perbandingan kurang daripada atau sama bagi dua objek Dalam Python, kita boleh mentakrifkan operasi perbandingan antara objek dengan menggunakan kaedah khas. Salah satunya ialah fungsi __le__(), yang digunakan untuk menentukan perbandingan kurang daripada atau sama. Fungsi __le__() ialah kaedah ajaib dalam Python dan merupakan fungsi khas yang digunakan untuk melaksanakan operasi "kurang daripada atau sama". Apabila kita membandingkan dua objek menggunakan operator kurang daripada atau sama (<=), Python

Apakah pengaturcaraan berorientasikan objek? Pengaturcaraan berorientasikan objek (OOP) ialah paradigma pengaturcaraan yang mengabstrak entiti dunia sebenar ke dalam kelas dan menggunakan objek untuk mewakili entiti ini. Kelas mentakrifkan sifat dan tingkah laku objek, dan objek memberi contoh kelas. Kelebihan utama OOP ialah ia menjadikan kod lebih mudah difahami, diselenggara dan digunakan semula. Konsep Asas OOP Konsep utama OOP termasuk kelas, objek, sifat dan kaedah. Kelas ialah pelan tindakan sesuatu objek, yang mentakrifkan sifat dan kelakuannya. Objek ialah contoh kelas dan mempunyai semua sifat dan tingkah laku kelas. Sifat ialah ciri-ciri objek yang boleh menyimpan data. Kaedah ialah fungsi objek yang boleh beroperasi pada data objek. Kelebihan OOP Kelebihan utama OOP termasuk: Kebolehgunaan semula: OOP boleh menjadikan kod lebih banyak
