您好,欢迎访问一九零五行业门户网

简单对比一下PHP 7 和 PHP 5 中的对象

本篇文章带大家了解一下php 7 和 php 5 中对象,并比较一下,看看它们之间的差异!
一、 class 介绍   php 中的 class、interface、trait 在底层均以 zend_class_entry 结构体实现
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 结构体中包含大量的指针以及 hashtable,这就导致结构体本身会占用不小的内存空间。另外,结构体中的指针还需要单独分配相应的内存空间,这又会消耗一部分内存空间。
⒈ 开发者自定义的 class 与 php 内部定义的 class 的比较  所谓开发者自定义的 class 即使用 php 语言定义的 class,而 php 内部定义的 class 是指 php 源代码中定义的 class 或 php 扩展中定义的 class。二者最本质的区别在于生命周期不同:
以 php-fpm 为例,当请求到来时,php 会解析开发者定义的 class 并为其分配相应的内存空间。其后在处理请求的过程中,php 会对这些 class 进行相应的调用,最后在处理完请求之后销毁这些 class,释放之前为其分配的内存空间。为了节约内存空间,不要在代码中定义一些实际并不使用的 class。可以使用 autoload 来屏蔽这些实际并不使用的 class,因为 autoload 只有在一个 class 被用到时才加载和解析,但这样就会把 class 的解析和加载过程由代码的编译阶段延后到代码的执行阶段,影响性能
另外需要注意的是,即使开启了 opcache 扩展,开发者自定义的 class 还是会随着请求的到来而解析和加载,随着请求的完成而销毁,opcache 只是提高了这两个阶段的速度
php 内部定义的 class 则不同。仍然以 php-fpm 为例,当一个 php-fpm 进程启动时,php 会为这些 class 一次性永久分配内存空间,直到此 php-fpm 进程消亡(为避免内存泄漏,php-fpm 会在处理完一定数量的请求之后销毁然后重启)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;}
由以上代码可以看出,在请求结束时,php 内部定义的 class 并不会被销毁。另外,由于 php 扩展中定义的 class 也属于 php 内部定义的 class 的范畴,所以,从节省内存空间的角度出发,不要开启一些自己并不使用的扩展。因为,如果扩展一旦开启,扩展中定义的 class 就会在 php-fpm 进程启动时被解析和加载。
很多时候,为了处理方便,我们会通过继承 \exception 来自定义 exception。但由于 zend_class_entry 结构体非常庞大,这就导致在提高便利的同时耗费了大量的内存
⒉ class 绑定   class 绑定指的是 class 数据的准备过程
  对于 php 内部定义的 class,绑定过程在 class 注册时就已经完成。此过程发生在 php 脚本运行之前,并且在整个 php-fpm 进程的生命周期中只发生一次。
  对于既没有继承 parent class,也没有实现 interface,也没有使用 trait 的 class,绑定过程发生在 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) ); } }}
对于实现了 interface 的 class 的绑定过程非常复杂,大致流程如下:
检查 interface 是否已经实现检查实现该 interface 的确实是一个 class,而不是 interface 自身(class、interface、trait 的底层数据结构都是 zend_class_entry)复制常量,并检查可能存在的冲突复制方法,并检查可能存在的冲突,除此之外还需要检查访问控制将 interface 加入到 zend_class_entry 的 **interfaces 中需要注意的是,所谓的复制只是将常量、属性、方法的引用计数加 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) { /*对于内部定义的 class,使用 realloc 分配内存,所分配的内存在进程的生命周期中永久有效*/ ce->interfaces = (zend_class_entry **) realloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num)); } else { /*对于开发者定义的 class,使用 erealloc 分配内存,所分配的内存只在请求的生命周期中有效*/ 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); }}
对于常量的复制,zval_add_ref 用于将常量的引用计数加1;而对于方法的复制,do_inherit_method 除了将相应方法的引用计数加 1 之外,还将方法中定义的静态变量的引用计数加 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; }}
对于实现了 interface 的 class 的绑定,由于要进行多次的循环遍历以及检查,通常非常消耗 cpu 资源,但却节省了内存空间。
现阶段,php 将 interface 的绑定推迟到了代码执行阶段进行,以为这每次请求都会进行这些操作
  对于 class 继承的绑定,过程与 interface 的绑定类似,但更为复杂。另外有一个值得注意的地方,如果 class 在绑定时已经解析到了父类,则绑定发生在代码编译阶段;否则发生在代码执行阶段。
// a 在 b 之前申明,b 的绑定发生在编译阶段class a { }class b extends a { }// a 在 b 之后申明,绑定 b 时编译器无法知道 a 情况,此时 b 的绑定只能延后到代码执行时class b extends a { }class a { }// 这种情况会报错:class b doesn't exist// 在代码执行阶段绑定 c,需要解析 b,但此时 b 有继承了 a,而 a 此时还是未知状态class c extends b { }class b extends a { }class a { }
如果使用 autoload,并且采用一个 class 对应一个文件的模式,则所有 class 的绑定都只会发生在代码执行阶段
二、php 5 中的 object⒈ object 中的方法  方法与函数的底层数据结构均为 zend_function。php 编译器在编译时将方法编译并添加到 zend_class_entry 的 function_table 属性中。所以,在 php 代码运行时,方法已经编译完成,php 要做的只是通过指针找到方法并执行。
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 尝试调用方法时,首先会在其对应的 class 的 function_table 中查找该方法,同时还会检查方法的访问控制。如果方法不存在或方法的访问控制不符合要求,object 会尝试调用莫属方法 __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 对大小写不敏感,所以所有的方法名称都会被转为小写(zend_str_tolower_copy())为了避免不必要的资源消耗,php 5.4 开始引入了 zend_literal 结构体,即参数 keytypedef struct _zend_literal { zval constant; zend_ulong hash_value; zend_uint cache_slot;} zend_literal;
其中,constant 记录了转为小写后的字符串,hash_value 则是预先计算好的 hash。这样就避免了 object 每次调用方法都要将方法名称转为小写并计算 hash 值。
class foo { public function bar() { } }$a = new foo;$b = 'bar';$a->bar(); /* good */$a->$b(); /* bad */
在上例中,在代码编译阶段,方法 bar 被转换成 bar 并添加到 zend_class_entry 的 function_table 中。当发生方法调用时:
第一种情形,在代码编译阶段,方法名称 bar 确定为字符串常量,编译器可以预先计算好其对应的 zend_literal 结构,即 key 参数。这样,代码在执行时相对会更快。第二种情形,由于在编译阶段编译器对 $b 一无所知,这就需要在代码执行阶段现将方法名称转为小写,然后计算 hash 值。⒉ object 中的属性  当对一个 class 进行实例化时,object 中的属性只是对 class 中属性的引用。这样,object 的创建操作就会相对轻量化,并且会节省一部分内存空间。
如果要对 object 中的属性进行修改,zend 引擎会单独创建一个 zval 结构,只对当前 object 的当前属性产生影响。
class 的实例化对应的会在底层创建一个 zend_obejct 数据结构,新创建的 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 {/*本质上是一个动态 object_bucket 数组*/ zend_object_store_bucket *object_buckets; zend_uint top; /*下一个可用的 handle,handle 取值从 1 开始。对应的在 *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 创建属性(对相应 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; }}
需要指出的是,在创建属性时,如果是非线程安全模式的 php,仅仅是增加相应属性的引用计数;但如果是线程安全模式的 php,则需要对属性进行深度复制,将 class 的属性全部复制到 object 中的 properties_table 中。
这也说明,线程安全的 php 比非线程安全的 php 运行慢,并且更耗费内存
每个属性在底层都对应一个 zend_property_info 结构:
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 与之相对应。properties_table 可以帮助我们快速确定一个 object 所访问的属性是否存在:
如果属性不存在,并且我们尝试向 object 写入该属性:如果 class 定义了 __set 方法,则使用 __set 方法写入该属性;否则会向 object 添加一个动态属性。但无论以何种方式写入该属性,写入的属性都将添加到 object 的 properties_table 中。如果属性存在,则需要检查相应的访问控制;对于 protected 和 private 类型,则需要检查当前的作用域。在创建完 object 之后,只要我们不向 object 中写入新的属性或更新 object 对应的 class 中的属性的值,则 object 所占用的内存空间不会发生变化。
属性的存储/访问方式:
zend_class_entry->properties_info 中存储的是一个个的 zend_property_info。而属性的值实际以 zval 指针数组的方式存储在 zend_class_entry->default_properties_table 中。object 中动态添加的属性只会以 property_name => property_value 的形式存储在 zend_object->properties_table 中。而在创建 object 时,zend_class_entry->properties_table 中的值会被逐个传递给 zend_object->properties_table。
zend_literal->cache_slot 中存储的 int 值为 run_time_cache 中的索引 index。run_time_cache 为数组结构,index 对应的 value 为访问该属性的 object 对应的 zend_class_entry;index + 1 对应的 value 为该属性对应的 zend_property_info 。在访问属性时,如果 zend_literal->cache_slot 中的值不为空,则可以通过 zend_literal->cache_slot 快速检索得到 zend_property_info 结构;如果为空,则在检索到 zend_property_info 的信息之后会初始化 zend_literal->cache_slot。
属性名称的存储方式
private 属性:"\0class_name\0property_name"
protected 属性:"\0*\0property_name"
public 属性:"property_name"
   执行以下代码,看看输出结果
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);
以上代码中,当为 foo动态设置foo 动态设置foo动态设置bar 属性时会调用 __set 方法。但 $bar 属性在 foo 中并不存在,按照常理,此时又会递归调用 __set 方法。为了避免这种递归调用,php 会使用 zend_guard 来判断当前是否已经处于重载方法的上下文中。
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 并不是引用传递。之所以会出现 object 是引用传递的假象,原因在于我们传递给函数的参数中所存储的只是 object 在 zend_objects_store 中的 id(handle)。通过这个 id,我们可以在 zend_objects_store 中查找并加载真正的 object,然后访问并修改 object 中的属性。
php 中,函数内外是两个不同的作用域,对于同一变量,在函数内部对其修改不会影响到函数外部。但通过 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 关键字、unserialize 函数、反射、clone 四种方式。
⒋ $this  $this 在使用时会自动接管当前对象,php 禁止对 this进行赋值操作。任何对this 进行赋值操作。任何对this进行赋值操作。任何对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 中进行方法调用时,对应执行的 opcode 为 init_method_call。以 $a->foo() 为例,在 init_method_call 中,zend 引擎知道是由 $a 发起的方法调用,所以 zend 引擎会把 $a 的值存入全局空间。在实际执行方法调用时,对应执行的 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;}
在实际执行方法体中的代码时,如果出现使用 $this 进行方法调用或属性赋值的情况,如 $this->a = 8 对应的将执行 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 引擎在构建方法堆栈时,$this 会被存入符号表,就像其他的变量一样。这样,当使用 $this 进行方法调用或将 $this 作为方法的参数时,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)); } }}
最后是关于作用域的问题,当进行方法调用时,zend 引擎会将作用域设置为 eg(scope)。eg(scope) 是 zend_class_entry 类型,也就是说,在方法中任何关于 object 的操作的作用域都是 object 对应的 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 对应的 class
⒌ 析构方法 destruct  在 php 中,不要依赖 destruct 方法销毁 object。因为当 php 发生致命错误时,destruct 方法并不会被调用。
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();}
在调用 destruct 方法时,首先会从后往前遍历整个符号表,调用所有引用计数为 1 的 object 的 destruct 方法;然后从前往后遍历全局 object store,调用每个 object 的 destruct 方法。在此过程中如果有任何错误发生,就会停止调用 destruct 方法,然后将所有 object 的 destruct 方法都标记为已调用过的状态。
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 引用计数加 1 */"destroyed bar""destroyed foo"// 示例 4class 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 中对象的销毁分为两个阶段:首先调用 destruct 方法(zend_object_store_bucket->bucket->obj->zend_objects_store_dtor_t),然后再释放内存(zend_object_store_bucket->bucket->obj->zend_objects_free_object_storage_t)。
之所以分为两个阶段执行是因为 destruct 中执行的是用户级的代码,即 php 代码;而释放内存的代码在系统底层运行。释放内存会破坏 php 的运行环境,为了使 destruct 中的 php 代码能正常运行,所以分为两个阶段,这样,保证在释放内存阶段 object 已经不被使用。
三、php 7 中的 object  与 php 5 相比,php 7 中的 object 在用户层并没有基本没有什么变化;但在底层实现上,在内存和性能方面做了一些优化。
⒈ 在内存布局和管理上的优化   ① 首先,在 zval 中移除了之前的 zend_object_value 结构,直接嵌入了 zend_object。这样,既节省了内存空间,同时提高了通过 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 存储在一块连续的内存空间。同时,properties_table 中直接存储了属性的 zval 结构。
   ③ guards 不再出现在 zend_object 中。如果 class 中定义了魔术方法( __set、__get、__isset、__unset ),则 guards 存储在 properties_table 的第一个 slot 中;否则不存储 guards。
   ④ zend_object_store 及 zend_object_store_bucket 被移除,取而代之的是一个存储各个 zend_object 指针的 c 数组,handle 为数组的索引。此外,之前 bucket 中存储的 handlers 现在移入 zend_object 中;而之前 bucket 中的 dtor、free_storege、clone 现在则移入了 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 内存的连续,所以自定义 object 中的 zend_object 只能放在最后。而 zval 中存储的只能是 zend_object,为了能通过 zend_object 顺利解析出 custom_object ,在 zend_object 的 handlers 中记录了 offset。
推荐学习:《php视频教程》
以上就是简单对比一下php 7 和 php 5 中的对象的详细内容。
其它类似信息

推荐信息