zend_object_value
, this structure in PHP 7 was abandoned. /* 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 */ };
zend_refcounted_h
, which is part of the new zval and garbage collection mechanism.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.properties_table
of the zval vector, a structural hack is used in the C language; it is useful for creating custom objects.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.zend_object
: /* PHP 5 */ typedef struct _my_own_object { zend_object zobj; my_custom_type *my_buffer; } my_own_object;
/* 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;
void*
. If you have not customized anything, you need to represent it as a zend_object*
, otherwise - as my_own_object*
.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. /* PHP 7 */ zend_object *zobj; zobj = Z_OBJ_P(this_zval);
/* PHP 7 */ typedef struct _my_own_object { my_custom_type *my_buffer; zend_object zobj; } my_own_object;
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. /* 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));
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.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));
/* PHP 7 */ typedef struct _my_own_object { void *my_custom_buffer; zend_object zobj; /* MUST be the last element */ } my_own_object;
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; }
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.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; }
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.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 .__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.zend_objects_destroy_object()
from your custom destructor, the custom __destruct()
will not be initiated. /* 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() */ }
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); /* , */ }
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 .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()
.Source: https://habr.com/ru/post/275497/
All Articles