Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Differences between objects in PHP 7 and PHP 5

2025-03-10 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article introduces the knowledge of "differences between objects in PHP 7 and PHP 5". Many people will encounter this dilemma in the operation of actual cases, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

1. Introduction of class

Class, interface and trait in    PHP are all implemented in zend_class_entry structure at the bottom.

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;}

The    zend_class_entry structure contains a large number of pointers and hashtable, which causes the structure itself to take up a lot of memory space. In addition, the pointers in the structure need to allocate the corresponding memory space separately, which in turn consumes some memory space.

Comparison between class defined by ⒈ developers and class defined internally by PHP

   's so-called developer-defined class uses class defined in PHP language, while class defined in PHP refers to class defined in PHP source code or class defined in PHP extension. The most essential difference between the two is the life cycle:

Take php-fpm as an example. When a request arrives, PHP parses the class defined by the developer and allocates the corresponding memory space. Later, during the processing of the request, the PHP calls these class accordingly, and finally destroys the class after processing the request, freeing up the memory space previously allocated to it.

To save memory space, don't define class in your code that you don't actually use. You can use autoload to shield these class that are not actually used, because autoload is loaded and parsed only when a class is used, but this will delay the parsing and loading process of class from the compilation phase of the code to the execution phase of the code, affecting performance

In addition, it should be noted that even if the OPCache extension is enabled, the developer-defined class will still be parsed and loaded with the request, and destroyed with the completion of the request. OPCache only increases the speed of these two phases.

The class defined internally by PHP is different. Still take php-fpm as an example. When a php-fpm process starts, PHP permanently allocates memory space for those class at one time until the php-fpm process dies (to avoid memory leaks, php-fpm destroys and then restarts after processing a certain number of requests)

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;}

   as you can see from the above code, the class defined within PHP will not be destroyed at the end of the request. In addition, since the class defined in the PHP extension also belongs to the class defined within PHP, do not open extensions that you do not use from the point of view of saving memory space. Because, once the extension is turned on, the class defined in the extension will be parsed and loaded when the php-fpm process starts.

Most of the time, for convenience, we will customize the exception by inheriting\ Exception. But because the zend_class_entry structure is very large, it consumes a lot of memory while improving the convenience.

⒉ class binding

   class binding refers to the preparation of class data

   for class defined internally by PHP, the binding process is completed when class registers. This process occurs before the PHP script runs, and only once throughout the life cycle of the php-fpm process.

   for class that neither inherits parent class nor implements interface nor uses trait, the binding process takes place during the editing phase of the PHP code and does not consume too many resources. Such class bindings usually only need to register the class with the class_table and check whether the class contains abstract methods but is not declared as an abstract type.

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);}

The binding process of    for class that implements interface is very complex, and the general process is as follows:

Check whether interface has been implemented

Check that it is indeed a class that implements the interface, not the interface itself (the underlying data structures of class, interface, and trait are all zend_class_entry)

Copy constants and check for possible conflicts

Copy the method and check for possible conflicts, in addition to checking access control

Add interface to zend_class_entry 's * * interfaces

It should be noted that the so-called replication only adds 1 to the reference count of constants, properties, and methods.

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) {/ * for internally defined class Using realloc to allocate memory, the allocated memory process is permanently valid for the life cycle of the memory process * / ce- > interfaces = (zend_class_entry * *) realloc (ce- > interfaces, sizeof (zend_class_entry *) * (+ + current_iface_num)) } else {/ * for developer-defined class, use erealloc to allocate memory, and the allocated memory is only valid for the life cycle of the request * / 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);}}

   for constant replication, zval_add_ref is used to increase the reference count of constants by 1; for method replication, do_inherit_method increases the reference count of static variables defined in the method by 1 in addition to the reference count of the corresponding method.

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;}}

The binding of    to class that implements interface usually consumes a lot of CPU resources but saves memory space due to multiple loop traverses and checks.

At this stage, PHP postponed the binding of interface to the code execution phase, thinking that these operations would be done every time the request was made.

The binding of    to class inheritance is similar to that of interface, but is more complex. It is also worth noting that if the class has resolved to the parent class at binding time, the binding occurs during the code compilation phase; otherwise, it occurs during the code execution phase.

/ / A declares before B that the binding of B occurs during the compilation phase class A {} class B extends A {} / A declares after B that the compiler cannot know about A when binding B, and that the binding of B can only be delayed until class B extends A {} class A {} / / this situation will cause an error: Class B doesn't exist// binds C during code execution and needs to parse B But at this time, B has inherited A, while An is still unknown status class C extends B {} class B extends A {} class A {}

If you use autoload and use the mode that a class corresponds to a file, all class bindings will only occur during code execution

II. Methods in object ⒈ object in PHP 5

The underlying data structure of both    methods and functions is zend_function. The PHP compiler compiles and adds methods to the function_table property of zend_class_entry at compile time. So, when the PHP code runs, the method has been compiled, and all PHP has to do is find the method through the pointer and execute it.

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

   when object attempts to call a method, it first looks for the method in the function_table of its corresponding class, and also checks the access control of the method. If the method does not exist or the access control of the method does not meet the requirements, object attempts to call the method _ _ 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 (& 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 E_ERROR% 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 (& 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 E_ERROR% 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;}

What    needs to point out here is:

Because PHP is case-insensitive, all method names are converted to lowercase (zend_str_tolower_copy ())

In order to avoid unnecessary resource consumption, PHP 5.4 began to introduce the zend_literal structure, the parameter key.

Typedef struct _ zend_literal {zval constant; zend_ulong hash_value; zend_uint cache_slot;} zend_literal

In   , constant records the lowercase string, and hash_value is a pre-calculated hash. This avoids the need for object to lowercase the method name and calculate the hash value each time the method is called.

Class Foo {public function BAR () {}} $a = new Foo;$b = 'bar';$a- > bar (); / * good * / $a-> $b (); / * bad * /

   in the above example, during the code compilation phase, the method BAR is converted to bar and added to the function_table of zend_class_entry. When a method call occurs:

In the first case, during the code compilation phase, the method name bar is determined as a string constant, and the compiler can pre-calculate its corresponding zend_literal structure, that is, the key parameter. In this way, the code executes relatively faster.

In the second case, since the compiler knows nothing about $b at compile time, you need to convert the method name to lowercase at code execution, and then calculate the hash value.

Properties in ⒉ object

   when instantiating a class, the properties in the object are simply references to the properties in the class. In this way, the creation of object will be relatively lightweight and save some memory space.

   if you want to modify the properties in the object, the zend engine creates a separate zval structure that affects only the current properties of the current object.

The instantiation of    class creates a zend_obejct data structure at the bottom, and the newly created object is registered with zend_objects_store. Zend_objects_store is a global object registry in which the same object can only be registered once.

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 {/ * is essentially a dynamic object_bucket array * / zend_object_store_bucket * object_buckets; zend_uint top; / * the next available handle,handle value starts at 1. The corresponding index in * object_buckets is the maximum length of * object_buckets currently assigned * / int free_list_head; / * handle-1 / zend_uint size; / * when the bucket in * object_bucket is terminated, the index of the bucket in * object_buckets will be added to the free_list linked list in order. Free_list_head is the first value in the linked list * /} zend_objects_store;typedef struct _ zend_object_store_bucket {zend_bool destructor_called; zend_bool valid; / * a value of 1 indicates that the current bucket is used, and the store_object in the store_bucket is used A value of 0 means that no valid object is stored in the current bucket, and the free_list in the store_bucket is used * / 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; / * the index of the first unused bucket is always stored in the free_list_head of zend_object_store, so the next only needs to record the index*/} free_list;} bucket;} zend_object_store_bucket of the first unused bucket after the current 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;}

After    registers object with zend_objects_store, an attribute is created for object (a reference to the corresponding class attribute)

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; table +) {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;}}

What    needs to point out is that when creating attributes, if it is a PHP in non-thread-safe mode, it only increases the reference count of the corresponding attributes; but if it is a PHP in thread-safe mode, you need to deeply copy the attributes and copy all the attributes of class to the properties_table in object.

It also shows that thread-safe PHP runs slower and consumes more memory than non-thread-safe PHP.

Each attribute corresponds to a zend_property_info structure at the bottom:

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

For each attribute declared in    class, there is a zend_property_info corresponding to it in properties_table in zend_class_entry. Properties_table can help us quickly determine whether a property accessed by an object exists:

If the attribute does not exist, and we try to write it to object: if class defines the _ _ set method, write it using the _ _ set method; otherwise, a dynamic attribute will be added to object. However, no matter how the attribute is written, the written attribute is added to the properties_table of object.

If the attribute exists, you need to check the appropriate access control; for protected and private types, you need to check the current scope.

After creating the object, as long as we do not write new properties to the object or update the values of the properties in the class corresponding to the object, the memory space occupied by the object will not change.

The storage / access method of the property:

Individual zend_property_info is stored in zend_class_entry- > properties_info. The value of the attribute is actually stored in zend_class_entry- > default_properties_table as an array of zval pointers. Attributes that are dynamically added in object are only stored in zend_object- > properties_table in the form property_name = > property_value. When you create an object, the values in zend_class_entry- > properties_table are passed to zend_object- > properties_table one by one.

The int value stored in zend_literal- > cache_slot is the index index in run_time_cache. The run_time_cache is an array structure, and the value corresponding to the index is the zend_class_entry;index + 1 corresponding to the object accessing the attribute, and the value corresponding to the attribute is the zend_property_info corresponding to the attribute. When accessing the property, if the value in zend_literal- > cache_slot is not empty, you can quickly retrieve the zend_property_info structure through zend_literal- > cache_slot; if it is empty, zend_literal- > cache_slot is initialized after the information of zend_property_info is retrieved.

How attribute names are stored

Private attribute: "\ 0class_name\ 0property_name"

Protected attribute: "\ 0*\ 0property_name"

Public attribute: "property_name"

   executes the following code to see the output

Class A {private $a = 'asides; protected $b =' baked; public $c = 'cached;} 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 ())

The role of guards in zend_object

The role of guards is to provide recursive protection against overloading of object.

Class Foo {public function _ set ($name, $value) {$this- > $name = $value;}} $foo = new Foo;$foo- > bar = 'baz';var_dump ($foo- > bar)

In the above    code, the _ _ set method is called when the foo dynamic setting bar property is dynamically set for foo. However, the $bar attribute does not exist in the Foo, and according to common sense, the _ _ set method is called recursively. To avoid such recursive calls, PHP uses zend_guard to determine whether it is currently in the context of an overloaded method.

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 reference passing

   first needs to declare that object is not reference passing. The reason for the illusion that object is passed by reference is that what we pass to the function is only the ID (handle) of object in zend_objects_store. With this ID, we can find and load the real object in zend_objects_store, and then access and modify the properties in object.

In PHP, there are two different scopes inside and outside the function. For the same variable, modifying it inside the function will not affect the outside of the function. However, accessing and modifying the properties of object through object's ID (handle) is not subject to this restriction.

$a = 1 echo function test ($a) {$a = 3; echo $a; / / output 3} test ($a); echo $a; / / output 1

The same object is stored only once in the zend_objects_store. To write a new object to zend_objects_store, you can only use the new keyword, unserialize function, reflection, and clone.

⒋ $this

   $this automatically takes over the current object when it is used, and PHP forbids assignment to this. Any assignment to this. Any assignment to this. Any assignment to this will cause an error

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 (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");} / *. * /

When    makes a method call in PHP, the corresponding OPCode is INIT_METHOD_CALL. Take $a-> foo () as an example. In INIT_METHOD_CALL, the Zend engine knows that the method call is initiated by $a, so the Zend engine stores the value of $an in the global space. When the method call is actually executed, the corresponding OPCode is DO_FCALL. In DO_FCALL, the Zend engine assigns the $a previously stored in global space to the pointer to $this, that is, 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;}

When    actually executes the code in the method body, if $this is used for method call or attribute assignment, for example, OPCode ZEND_ASSIGN_OBJ will be executed corresponding to $this- > a = 8, then the value of $this will be obtained from EG (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;}}

When the    Zend engine builds the method stack, $this is stored in the symbol table, just like any other variable. In this way, when you use $this to make a method call or take $this as a parameter to a method, the Zend engine will get $this from the symbol table.

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));}

Finally,    is about scope. When a method call is made, the Zend engine sets the scope to EG (scope). EG (scope) is of type zend_class_entry, that is, the scope of any operation on object in a method is the class corresponding to object. The same is true for checking the access control of a property:

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;}

   is precisely because of the above features, so the following code works properly

Class A {private $a; public function foo (A $obj) {$this- > a = 'foo'; $obj- > a =' bar'; / * yes, this is possible * /}} $a = new Ateria-> foo ($b)

The scope of object in PHP is class corresponding to object.

⒌ destructing method destruct

   in PHP, do not rely on the destruct method to destroy the object. Because the destruct method is not called when a fatal error occurs in PHP.

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 ();}

When    calls the destruct method, it first traverses the entire symbol table from back to front, calling the destruct method of all references to object with a count of 1, and then traverses the global object store from back to back, calling the destruct method of each object. If any errors occur during this process, the call to the destruct method is stopped and all object's destruct methods are marked as called.

Class Foo {public function _ destruct () {var_dump ("destroyed Foo");}} class Bar {public function _ destruct () {var_dump ("destroyed Bar");}} / / example 1$ a = new Foo;$b = new Bar; "destroyed Bar"destroyed Foo" / / example 2$ a = new Bar;$b = new Foo; "destroyed Foo"destroyed Bar" / / sample 3$ a = new Bar;$b = new Foo;$c = $b / * $b reference count plus 1 * / "destroyed Bar"destroyed Foo" / / example 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 potential b = new Bar;$b2 = $b; "destroyed Foo"

   in addition, do not add any important code to the destruct method

Class Foo {public function _ _ destruct () {new Foo;} / * PHP will eventually crash * /}

The destruction of objects in PHP is divided into two phases: first call the destruct method (zend_object_store_bucket- > bucket- > obj- > zend_objects_store_dtor_t), and then free memory (zend_object_store_bucket- > bucket- > obj- > zend_objects_free_object_storage_t).

Execution is divided into two phases because what is executed in destruct is user-level code, that is, PHP code, while the code that frees memory runs at the bottom of the system. Freeing memory destroys the running environment of PHP. In order to make the PHP code in destruct work properly, it is divided into two phases to ensure that object is no longer used during the free memory phase.

3. Object in PHP 7

Compared to PHP 5, object in    in PHP 7 is virtually unchanged at the user level; however, in the underlying implementation, some optimizations have been made in terms of memory and performance.

Optimization of memory layout and Management in ⒈

First of all,    ① removes the previous zend_object_value structure from zval and embeds zend_object directly. In this way, the memory space is saved and the efficiency of finding zend_object through zval is improved.

/ * zend_object*/struct _ zend_object in PHP 7 {zend_refcounted gc; uint32_t handle; zend_class_entry * ce; const zend_object_handlers * handlers; HashTable * properties; zval properties_table [1];}; / * zend_object_value*/typedef struct _ zend_object_value {zend_object_handle handle; const zend_object_handlers * handlers in PHP 5 } zend_object_value

   accesses object through zval in PHP 5, first finding handle through zend_object_value in zva, then finding zend_object_store_bucket in zend_object_store through handle, and then parsing object from bucket. In PHP 7, the address pointer of zend_object is stored directly in zval.

   ② secondly, properties_table takes advantage of the struct hack feature, which makes zend_object and properties_table stored in a contiguous memory space. At the same time, the zval structure of the attribute is directly stored in properties_table.

   ③ guards no longer appears in zend_object. If magic methods (_ _ set, _ _ get, _ _ isset, _ _ unset) are defined in class, the guards is stored in the first slot of properties_table; otherwise, guards is not stored.

   ④ zend_object_store and zend_object_store_bucket are removed and replaced by a C array that stores individual zend_object pointers, and handle is the index of the array. In addition, the handlers previously stored in bucket is now moved into zend_object, while the dtor, free_storege, and clone in bucket are now moved into 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 * / / Other same as PHP 5}; changes to the underlying custom object of ⒉ (custom object will be used in PHP extensions) / * custom_object*/struct custom_object {zend_object std; my_custom_type * my_buffer; / / in PHP 5; / * custom_object*/struct custom_object {my_custom_type * my_buffer; / / in PHP 7. Zend_object std;}

   because the struct hack feature is used in the zend_object of PHP 7 to ensure the continuity of zend_object memory, the zend_object in the custom object can only be placed last. What can only be stored in zval is zend_object. In order to parse custom_object smoothly through zend_object, offset is recorded in the handlers of zend_object.

This is the end of the introduction to "differences between objects in PHP 7 and PHP 5". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report