📜 ⬆️ ⬇️

Objects in PHP 7

image

Today, PHP developers are working on a C-level API. In this post, I will mostly talk about PHP's internal development, although if there is something interesting from the user level, I will digress and explain .

Changes in objects compared to PHP 5


In order to fully understand the topic of objects in PHP, I recommend that you first familiarize yourself with the post detailing objects and classes in PHP .

So, what has changed in the seventh version compared to the fifth?
')

Object structure and memory management


First of all, you can say goodbye to zend_object_value , this structure in PHP 7 was abandoned.

Let's see an example of the definition of a zend_object object:

 /* in PHP 5 */ typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; } zend_object; /* in PHP 7 */ struct _zend_object { zend_refcounted_h gc; uint32_t handle; zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1]; /* C struct hack */ }; 

As you can see, there are some minor differences from PHP 5.

First, it contains the header zend_refcounted_h , which is part of the new zval and garbage collection mechanism.

Secondly, the object now contains its handle , while in PHP 5 this task was performed by zend_object_store . And in PHP 7, the object store (object store) has much less responsibilities.

Thirdly, to substitute the properties_table of the zval vector, a structural hack is used in the C language; it is useful for creating custom objects.

Manage custom objects


An important change was the management of custom objects that we create for our needs. Now they include zend_object . This is a very important feature of the Zend Engine object model: extensions can declare their own objects and manage them, developing the capabilities of the standard implementation of objects in Zend without changing the source code of the engine.

In PHP 5, we simply create inheritance in the form of a C-structure, including the basic definition of zend_object :

 /* PHP 5 */ typedef struct _my_own_object { zend_object zobj; my_custom_type *my_buffer; } my_own_object; 

Due to the inheritance of the C-structure, it is enough for us to create a simple construction:

 /* PHP 5 */ my_own_object *my_obj; zend_object *zobj; my_obj = (my_own_object *)zend_objects_store_get_object(this_zval); zobj = (zend_object *)my_obj; 

You may have noticed that when you receive zval in PHP 5, for example, $ this in OO methods, you cannot get access to the object to which it directly points from within this zval. To do this, you will have to contact the object storage. Extract the handler from zval (in PHP 5) and use it to ask the repository to return the found object. This object — it can be custom — is returned as void* . If you have not customized anything, you need to represent it as a zend_object* , otherwise - as my_own_object* .

In short, to get an object from a method, in PHP 5 you need to perform a search procedure. And this is not very good for performance.

In PHP 7, everything is different. The object — whether it is a custom or a classic zend_object — is stored directly in zval. However, object storage no longer supports retrieval . That is, you can no longer read the contents of the object store, just write to it or erase it.

The placed object is entirely embedded in zval , so if you call zval as a parameter and want to get the memory area of ​​the object it points to, then you will not need to perform any additional searches. Here’s how to get an object in PHP 7:

 /* PHP 7 */ zend_object *zobj; zobj = Z_OBJ_P(this_zval); 

Much easier than PHP 5, isn't it?

Otherwise, work with custom placement of objects is now arranged. From the above code it can be seen that the only way to get a custom object is to manipulate the memory: move the pointer in any desired direction within the required amount of memory. Pure C programming and high performance: you will most likely stay in the same physical memory page, and therefore the kernel will not load the new page.

Declaring a custom object in PHP 7:

 /* PHP 7 */ typedef struct _my_own_object { my_custom_type *my_buffer; zend_object zobj; } my_own_object; 

Pay attention to the permutation of the components of the structure in comparison with PHP 5. What is it done for? When you read a zend_object from a zval, then to get your my_own_object you have to take the memory in the opposite direction, subtracting the offset (offset) of zend_object 'in the structure. This is done using OffsetOf() from stddef.h (if necessary, you can easily emulate). This is considered the use of an advanced C-structure, but if you know the language you are using well (and it shouldn't be any other way), then you probably already had to do this.

To get a custom object in PHP 7, do the following:

 /* PHP 7 */ zend_object *zobj; my_own_object *my_obj; zobj = Z_OBJ_P(this_zval); my_obj = (my_own_object *)((char *)zobj - XoffsetOf(struct my_own_object, zobj)); 

Here, the use of offsetof() introduces some confusion: your_custom_struct should be the last component of zend_object . Obviously, if you declare types after this, then due to the zend_object organization of the placement of zend_object in PHP 7, you will later have difficulty accessing these types.

Do not forget that in PHP 7 zend_object now uses a structural hack. This means that the allocated memory will differ from sizeof(zend_object) . zend_object placement:

 /* PHP 5 */ zend_object *zobj; zobj = ecalloc(1, sizeof(zend_object)); /* PHP 7 */ zend_object *zobj; zobj = ecalloc(1, sizeof(zend_object) + zend_object_properties_size(ce)); 

Since your class knows everything about the declared attributes, it determines the size of the memory that you must allocate for the components.

Object creation


Consider a real example. Suppose we have a custom object:

 /* PHP 7 */ typedef struct _my_own_object { void *my_custom_buffer; zend_object zobj; /* MUST be the last element */ } my_own_object; 

This is what its create_object() handler might look like:

 /* PHP 7 */ static zend_object *my_create_object(zend_class_entry *ce) { my_own_object *my_obj; my_obj = ecalloc(1, sizeof(my_obj) + zend_object_properties_size(ce)); my_obj->my_custom_buffer = emalloc(512); /* ,      512  */ zend_object_std_init(&my_obj->zobj, ce); /*      zend_object! */ object_properties_init(&my_obj->zobj, ce); my_obj->zobj.handlers = &my_class_handlers; /*     ,      */ return &my_obj->zobj; } 

Unlike PHP 5, you should not forget about the amount of memory allocated: remember the structural hack, replacing the properties of zend_object . In addition, object storage is no longer used here. In PHP 5, an object creation handler had to register it with the repository, and then pass some function pointers for future destruction and release of the object. In PHP 7, this no longer needs to be done, the create_object() function works much more clearly.

To use this custom handler, create_object() need to declare it in your extension. Thus, you will declare each handler:

 /* PHP 7 */ zend_class_entry *my_ce; zend_object_handlers my_ce_handlers; PHP_MINIT_FUNCTION(my_extension) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "MyCustomClass", NULL); my_ce = zend_register_internal_class(&ce); my_ce->create_object = my_create_object; /*    */ memcpy(&my_ce_handlers, zend_get_std_object_handlers(), sizeof(my_ce_handlers)); my_ce_handlers.free_obj = my_free_object; /*  free */ my_ce_handlers.dtor_obj = my_destroy_object; /*  dtor */ /*   my_ce_handlers.clone_obj,        */ my_ce_handlers.offset = XtOffsetOf(my_own_object, zobj); /*       */ return SUCCESS; } 

As you can see, in MINIT we declare free_obj() and dtor_obj() . In PHP 5, when registering an object in the repository, both of them need to be declared in zend_objects_store_put() , but in PHP 7 this is no longer necessary . Now zend_object_std_init() will write the object to the storage itself; you do not need to do this manually, so do not forget about this call.

So, we have registered our free_obj() and dtor_obj() , as well as the offset component, used when calculating the location of our custom object in memory. This information is needed by the engine, because now it is he who deals with the release of objects, not you . In PHP 5, this was done manually, usually with free() . And since now it makes the engine, then to release the entire pointer, it needs to get not only the zend_object types, but also the offset value for your custom structure. An example can be found here .

Destruction of an object


I want to remind you that the destructor is called when an object is destroyed at the PHP user level, just like __destruct() called. So in case of critical errors, the destructor may not be called at all, and in PHP 7 this situation has not changed. If you have carefully studied the post that was mentioned at the beginning, or this presentation , then, most likely, remember that the destructor should not leave the object in an unstable state, since the object once destroyed should be available in certain situations. Therefore, in PHP, handlers for the destruction and release of an object are separated from each other. The release handler is called when the engine is completely sure that the object is not used anywhere else. The destructor is called when the refcount of the object reaches 0, but since some custom code ( __destruct() ) can be executed, the current object cannot be reused anywhere as a reference, which means it must remain in an unstable state. Therefore, be very careful if you release the memory with a destructor. Usually the destructor stops using resources, but does not free them. The release handler is already doing this.

So let's summarize the work of the destructor:

 /* PHP 7 */ static void my_destroy_object(zend_object *object) { my_own_object *my_obj; my_obj = (my_own_object *)((char *)object - XoffsetOf(my_own_object, zobj)); /*    -   my_obj->my_custom_buffer,     ,    ,   -.     . */ zend_objects_destroy_object(object); /*  __destruct()    */ } 

Object storage release


The storage release function is initiated by the engine when it is absolutely sure that the object is not used anywhere else. Just before the object is destroyed, the engine calls the free_obj() handler. Have you allocated any resources in your custom create_object() handler? It is time to release them:

 /* PHP 7 */ static void my_free_object(zend_object *object) { my_own_object *my_obj; my_obj = (my_own_object *)((char *)object - XoffsetOf(my_own_object, zobj)); efree(my_obj->my_custom_buffer); /*    */ zend_object_std_dtor(object); /*   ,     */ } 

And that's all. You no longer need to deal with the release yourself, as in PHP 5. Previously, the handler ended up with something like free(object) ; Now, a place for your custom object structure is allocated in the create_object() handler, but when you pass the offset value to MINIT, it gets the opportunity to release itself. For example, like here .

Of course, in many cases, the free_obj() handler is called immediately after the dtor_obj() handler. The exception is when a user destructor sends $ this to someone or in the case of a custom extension object that is poorly designed. If you are interested in the complete code sequence when the object is freed by the engine, read about zend_object_store_del() .

Conclusion


We looked at how work with objects changed in PHP 7. At the user level, everything remained virtually unchanged, only the object model was optimized: it began to work faster and has a little more features. But there are no significant innovations.

But “under the hood” of change is much more. They are also not too large and will not require you many hours of study, but still you will need to make some effort. Both object models have become incompatible at a low level, so you have to rewrite that part of the source code of your extensions that relates to objects. In this post I tried to explain the difference. If in development you switch to PHP 7, you will notice that it has become clearer and more structured compared to PHP 5. The new version has freed itself from a heavy ten-year legacy. Many things in PHP 7 have been improved and redesigned to eliminate the need for code patches in a number of cases.

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


All Articles