📜 ⬆️ ⬇️

Details about objects and classes in PHP

Today objects are used very actively, although it was hard to imagine after the release of PHP 5 in 2005. Then I still knew little about the possibilities of this language. The fifth version of PHP was compared with the previous, fourth, and the main advantage of the new release was a new, very powerful object model. And today, ten years later, about 90% of all PHP code contains objects that have not changed since PHP 5.0. This convincingly indicates the role played by the introduction of the object model, which has repeatedly improved over the coming years. In this post I would like to talk about how everything is arranged "under the hood." So that people understand the essence of the processes - why it was done this way and not otherwise - and better, more fully used the possibilities of the language. I will also touch on the topic of memory usage by objects, including comparing with equivalent arrays (when possible).

I will talk about the PHP 5.4 version as an example, and the things I describe are valid for 5.5 and 5.6, because the object model device is almost unchanged there. Please note that in version 5.3, everything is not so good in terms of features and overall performance.

In PHP 7, which is still being actively developed, the object model is not much reworked, only minor changes have been made. Just because everything works so well, and the best is the enemy of the good. Opportunities that did not affect the core were added, but this will not be the case here.

As a demonstration, I'll start with synthetic benchmarks:
')
class Foo { public $a = "foobarstring"; public $b; public $c = ['some', 'values']; } for ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${'var'.$i} = new Foo; echo memory_get_usage() - $m"\n"; } 

A simple class with three attributes is declared here, and then 1000 objects of this class are created in a loop. Notice how this example uses memory: when creating an object of class Foo and a variable to store it, 262 bytes of PHP dynamic memory are allocated.

Let's replace an object with an equivalent array:

 for ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${'var'.$i} = [['some', 'values'], null, 'foobarstring']; echo memory_get_usage() - $m . "\n"; } 

In this case, the same elements are used: the array itself, null, and the string variable foobarstring . That's just consumed already 1160 bytes of memory, which is 4.4 times more.

Here is another example:

 $class = <<<'CL' class Foo { public $a = "foobarstring"; public $b; public $c = ['some', 'values']; } CL; echo memory_get_usage() . "\n"; eval($class); echo memory_get_usage() . "\n"; 

Since the class is declared at compile time, we use the eval() operator to declare and measure the memory used (using the PHP memory manager eval() . In this case, no objects in this code are not created. The volume of involved memory (diff memory) is 2216 bytes.

Now let's look at how it all works in the depths of PHP, supported by theory of practical observations.

It all starts with classes


Inside the PHP class is represented using the structure 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; }; 

The size of the structure, based on the LP64 model, is 568 bytes . That is, each time PHP declares a class, it is forced to create zend_class_entry , using more than half a kilobyte of dynamic memory for this purpose. Of course, this is not limited to this: as you noticed, the structure contains quite a few pointers, which also need to be placed in memory. That is, classes themselves consume much more memory than all objects created from them afterwards.

Among other things, classes contain attributes (static and dynamic), as well as methods. All this also requires memory. As for the methods, it is difficult to calculate the exact dependency, but one thing is true: the larger the method body, the more OPArray it OPArray , which means the more memory it consumes. Add to this static variables that can be declared in the method. Next are the attributes, later they will also be placed in memory. The volume depends on their default values: the integers will take a little, but a large static array will eat a lot of memory.

It is important to know about one more thing related to zend_class_entry - about PHP comments. They are also known as annotations. These are string variables (in C language, char* buffers) that must also be stored in memory. For C, which does not use Unicode, unlike PHP, the rule is very simple: one character = one byte. The more annotations you have in a class, the more memory will be used after parsing.

In zend_class_entry field contains class annotations. Methods and attributes also have such a field.

User and internal classes


A custom class is a class defined using PHP, and the inner class is defined either by embedding the source code in PHP itself, or using an extension. The biggest difference between these two types of classes is that user classes operate on memory allocated on request, while internal ones use “permanent” memory.

This means that when PHP finishes processing the current HTTP request, it removes from memory and destroys all user classes, preparing to process the next request. This approach is known as “the share nothing architecture”. So it was laid in PHP from the very beginning, and it is not planned to change it yet.

So, each time a query is formed and classes are parsed, memory is allocated for them. After using the class, everything connected with it is destroyed. So be sure to use all the declared classes, otherwise memory will be lost. Use autoloaders, they delay parsing / declaration at runtime, when PHP needs to use a class. Despite the slowdown in execution, the autoloader allows you to correctly use memory, since it will not run until you really need a class.

The inner classes are different. They are placed in the memory constantly, regardless of whether they are used or not. That is, they are destroyed only when PHP itself stops working - after the completion of processing all requests (implies SAPI web, for example, PHP-FPM). Therefore, internal classes are more efficient than user classes (at the end of the query, only static attributes are destroyed, nothing else).

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

Note that even when opcodes are cached, like OPCache, the class is created and destroyed with every request, as is the case with custom classes. OPCache just speeds up both of these processes.

As you noticed, if you activate a lot of PHP extensions, each of which declares a lot of classes, but only a small number of them are used, then the memory is lost. Remember that PHP extensions declare classes during PHP startup, even if these classes will not be used in subsequent requests. Therefore, it is not recommended to keep the extensions active if they are not being applied at the moment, otherwise you will lose memory. Especially if these extensions declare many classes - although they can clog the memory with something else.

Classes, interfaces or traits - no difference


To manage classes, interfaces, and traits in PHP, the same structure is zend_class_entry - zend_class_entry . And as you have seen, this structure is very cumbersome. Sometimes developers declare interfaces in code to be able to use their names in catch blocks. This allows you to catch only certain types of exceptions. For example:

 interface BarException { } class MyException extends Exception implements BarException { } try { $foo->bar(): } catch (BarException $e) { } 

It's not good that 912 bytes are used here, just to declare the BarException interface.

 $class = <<<'CL' interface Bar { } CL; $m = memory_get_usage(); eval($class); echo memory_get_usage() - $m . "\n"; /* 912 bytes */ 

I do not want to say that this is bad or stupid, I am not trying to blame anyone or anything. Just draw your attention to this moment. From the point of view of the internal structure of PHP, classes, interfaces and traits are used in exactly the same way. Attributes cannot be added to the interface, the parser or compiler simply will not allow this. However, the zend_class_entry structure zend_class_entry not go anywhere, just a number of fields, including the static_members_table , will not be pointers in memory. Declaring a class, equivalent trait, or equivalent interface will require the same amount of memory, since they all use the same structure.

Class binding


Many developers don’t recall class binding until they start asking questions about how things really work. Class binding can be described as “the process during which the class itself and all the data associated with it are prepared for the full use of the developer.” This process is very simple and does not require a lot of resources if we are talking about one class that does not complement the other, does not use traits and does not implement the interface. The binding process for such classes takes place entirely at compile time, and in the course of execution, resources are no longer spent on this. Please note that this was a binding class declared by the user. For inner classes, the same process is performed when the classes are registered with the kernel or PHP extensions, just before the user scripts are run — and this is done only once during the entire PHP run time.

Everything is much more complicated when it comes to the introduction of interfaces or the inheritance of classes. Then, during class binding, parent and child objects (whether classes or interfaces) copy everything.

 /* Single class */ 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; 

In the case of a simple class declaration, we run do_bind_class() . This function only registers a fully defined class in the class table for further use at runtime, and also checks for possible abstract methods:

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

There is nothing to add, a simple case.

When binding a class that implements an interface, the following actions should be taken:

By "copying" is meant not a complete deep copy. For constants, attributes, and functions, one by one recalculates how many entities in memory use them.

 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) { if (ce->type == ZEND_INTERNAL_CLASS) { ce->interfaces = (zend_class_entry **) realloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num)); } else { ce->interfaces = (zend_class_entry **) erealloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num)); } } ce->interfaces[ce->num_interfaces++] = iface; 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); 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); } } 

Note the difference between internal and custom classes. The first will use realloc() for memory allocation, the second - erealloc() . realloc() allocates “permanent” memory, and erealloc() “allocated on demand” memory.

You can see that when two constant tables (interface-1 and class-1) are combined, they do this with the zval_add_ref zval_add_ref . It does not copy constants from one table to another, but shares their pointers, simply by adding the number of references.

For each of the tables of functions (methods), do_inherit_method used:

 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 refcount is added to the OPArray function, and all possible static variables declared in the function are copied using zval_add_ref (here it is a method). Thus, a lot of computational resources are needed for the entire copying process, because many cycles and checks are involved here. But memory is used a little. Unfortunately, today the interface binding runs completely at runtime, and you will feel it with every request. Perhaps the developers will soon change it.

As for inheritance, here, in principle, everything is the same as in the implementation of the interface. Only more "participants" are involved. But I want to note that if PHP already knows about the class, then the binding is performed at compile time, and if it does not, at run time. So it is better to declare this:

 /* good */ class A { } class B extends A { } 

instead:

 /* bad */ class B extends A { } class A { } 

By the way, the routine class binding procedure can lead to very strange behavior:

 /*   */ class B extends A { } class A { } 


 /*    */ Fatal error: Class 'B' not found */ class C extends B { } class B extends A { } class A { } 


In the first variant, class B binding is postponed for execution time, because when the compiler reaches the declaration of this class, it still does not know anything about class A. When execution begins, class A binding occurs without question, because it is already compiled, being single class. In the second case, everything is different. The binding of class C is postponed until runtime, because the compiler still does not know anything about B, trying to compile it. But when class C starts at runtime, it searches for B, which does not exist, because it is not compiled because B is a complement. The message “Class B does not exist” flies.

Objects


So now we know that:



Now let's talk about the objects. The first chapter shows that the creation of a “classic” object (a “classic” user class) required very little memory, about 200 bytes. It's all about the class. Further compilation of the class also consumes memory, but this is for the better, because it takes less bytes to create a single object. In essence, an object is a tiny set of tiny structures.

Management of object methods


At the engine level, the methods and functions are the same - the structure of zend_function_structure . Only the names are different. Methods are compiled and added to the function_table attribute in zend_class_entry . Therefore, at run time, each method is presented; it is only a matter of translating a pointer to execution.

 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 an object tries to call a method, the default engine searches the table of values ​​of functions of the class of this object. If the method does not exist, __call() called. It also checks the visibility - public / protected / private - depending on what the following actions are taken:

 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); zend_str_tolower_copy(lc_method_name, method_name, method_len); hash_value = zend_hash_func(lc_method_name, method_len+1); } /* If the method is not found */ 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) { /* if the class has got a __call() handler */ return zend_get_user_call_function(zobj->ce, method_name, method_len); /* call the __call() handler */ } else { return NULL; /* else return NULL, which will likely lead to a fatal error : method not found */ } } /* Check access level */ if (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) { zend_function *updated_fbc; 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 { /* ... ... */ } 

You may have noticed an interesting thing, look at the first lines:

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

This is a manifestation of PHP immunity to different registers. The system must first reduce each function to lower case ( zend_str_tolower_copy() ) before calling it. Not really everyone, but those where there is an if . The key variable prevents the execution of a function that converts to lower case (the part with the else ) - this is part of the PHP optimization implemented in version 5.4. If the method call is not dynamic, then the compiler has already calculated the key , and less resources are spent during the execution.

 class Foo { public function BAR() { } } $a = new Foo; $b = 'bar'; $a->bar(); /* static call : good */ $a->$b(); /* dynamic call : bad */ 

/ . BAR() bar() .

: key “bar”, , . , “$b”, key . , , ( zend_hash_func() ), .

__call() , . , , .


:



, , , . , , , . — PHP. , . , , , $a Foo #2:



, , « » zend_object 32 :

 typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; /* protects from __get/__set ... recursion */ } zend_object; 

. , , zend_object_store . Zend — , :

 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; /* Add the object into the store */ 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; } 

:

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

, / ( ) zval* , . PHP refcount, Zend (ZTS, Zend thread safety), zval . , ZTS ZTS 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; 

() , property_info zend_class_entry . :

 class Foo { public $a = 'foo'; protected $b; private $c; } struct _zend_class_entry { /* ... ... */ HashTable function_table; HashTable properties_info; /* here are the properties infos about $a, $b and $c */ zval **default_properties_table; /* and here, we'll find $a, $b and $c with their default values */ int default_properties_count; /* this will have the value of 3 : 3 properties */ /* ... ... */ 

Properties_infos , . , object->properties . scope (public/protected/private).

, __set() . , object->property_table .

 property_info = zend_get_property_info_quick(zobj->ce, member, (zobj->ce->__set != NULL), key TSRMLS_CC); if (EXPECTED(property_info != NULL) && ((EXPECTED((property_info->flags & ZEND_ACC_STATIC) == 0) && property_info->offset >= 0) ? (zobj->properties ? ((variable_ptr = (zval**)zobj->properties_table[property_info->offset]) != NULL) : (*(variable_ptr = &zobj->properties_table[property_info->offset]) != NULL)) : (EXPECTED(zobj->properties != NULL) && EXPECTED(zend_hash_quick_find(zobj->properties, property_info->name, property_info->name_length+1, property_info->h, (void **) &variable_ptr) == SUCCESS)))) { /* ... ... */ } else { zend_guard *guard = NULL; if (zobj->ce->__set && /* class has a __set() ? */ zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS && !guard->in_set) { Z_ADDREF_P(object); if (PZVAL_IS_REF(object)) { SEPARATE_ZVAL(&object); } guard->in_set = 1; /* prevent circular setting */ if (zend_std_call_setter(object, member, value TSRMLS_CC) != SUCCESS) { /* call __set() */ } guard->in_set = 0; zval_ptr_dtor(&object); /* ... ... */ 

, . ( ), .

,


. :

 function foo($var) { $var = 42; } $o = new MyClass; foo($o); var_dump($o); /* this is still an object, not the integer 42 */ 

, « PHP 5 », . . , , . , , , .

, zval , , , , . . zval , .

object(MyClass)#1 (0) { } /* #1 is the object handle (number), it is unique */



Zend_object_store . new , unserialize() , reflection API clone . .

 typedef struct _zend_objects_store { zend_object_store_bucket *object_buckets; zend_uint top; zend_uint size; int free_list_head; } zend_objects_store; typedef struct _zend_object_store_bucket { zend_bool destructor_called; zend_bool valid; 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; } free_list; } bucket; } zend_object_store_bucket; 

$this?


$this , : , .. $this , — , «» . .

-, $this . , $this , .

 /* ... ... */ if (opline_is_fetch_this(last_op TSRMLS_CC)) { zend_error(E_COMPILE_ERROR, "Cannot re-assign $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; } } 

$this ? , OPCode INIT_METHOD_CALL . , , $a->foo() $a . $a . OPCode DO_FCALL . ( ) $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 , , , $this 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; } } 

, $this (, $this->foo() ) ( $this->foo($this); ), $this , . :

 if (op_array->this_var != -1 && EG(This)) { Z_ADDREF_P(EG(This)); 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)); } } } 

, :

 if (fbc->type == ZEND_USER_FUNCTION || fbc->common.scope) { /* ... ... */ EG(scope) = (fbc->type == ZEND_USER_FUNCTION || !EX(object)) ? fbc->common.scope : NULL; /* ... ... */ } 

EG(scope) zend_class_entry . , . , :

 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 — , , . “Foo”, Foo Foo, .


, , PHP :

 class Foo { public function __destruct() { echo "byebye foo"; } } $f = new Foo; thisfunctiondoesntexist(); /* fatal error, function not found, the Foo's destructor is NOT run */ 

, - ? :

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

:


:

class Foo { public function __destruct() { var_dump("destroyed Foo"); } }
class Bar { public function __destruct() { var_dump("destroyed Bar"); } }

:

 $a = new Foo; $b = new Bar; "destroyed Bar" "destroyed Foo" 

:

 $a = new Bar; $b = new Foo; "destroyed Foo" "destroyed Bar" 

:

 $a = new Bar; $b = new Foo; $c = $b; /* increment $b's object refcount */ "destroyed Bar" "destroyed Foo" 

:

 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() . PHP , . , .

PHP - . , Zend , , , , PHP. — . , PHP 7 - .

, PHP , __get() __set() . - , , ( 8 , ulimit –s ) PHP.

 class Foo { public function __destruct() { new Foo; } /* you will crash */ } 

: , , (lock mechanism), PHP . - , . PHP , refcount , , .

Conclusion


, . , . . , -, , .

Source: https://habr.com/ru/post/255237/


All Articles