tobj->constructed = 0;#if PHP_VERSION_ID < 50399
zend_hash_copy(tobj->std.properties, &(class_type->default_properties), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*));#else
object_properties_init(&tobj->std, class_type);#endif
zov.handle = zend_objects_store_put(tobj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC); zov.handlers = &object_handlers; return zov;} PHP_METHOD(testclass, construct) { zval *this = getThis(); test_object *tobj = zend_object_store_get_object(this TSRMLS_CC); assert(tobj != NULL); tobj->constructed = (zend_bool) 1; ... /* if there's an error that leaves the object in an invalid state and * you have to throw an exception, also destroy the $this reference. * The reason is that the exception may be caught in the constructor * of the child class that's calling this constructor. */ if (bad_thing_happened()) { /* Destroying only the $this reference will cause the object to leak; * it will be destroyed on request shutdown, but you can prevent that * by also destroying the object with: * zval_dtor(this); * But beware this will call both the destroy_object and the * free_object handlers. If you want only the second to be called, * you can call zend_object_store_ctor_failed() before */ ZVAL_NULL(this); } } static zend_function *get_constructor(zval *object TSRMLS_DC) { /* Could always return constr_wrapper_fun, but it's uncessary to call the * wrapper if instantiating the superclass */ if (Z_OBJCE_P(object) == ce_ptr) return zend_get_std_object_handlers()-> get_constructor(object TSRMLS_CC); else return &constr_wrapper_fun; } static void construction_wrapper(INTERNAL_FUNCTION_PARAMETERS) { zval *this = getThis(); test_object *tobj; zend_class_entry *this_ce; zend_function *zf; zend_fcall_info fci = {0}; zend_fcall_info_cache fci_cache = {0}; zval *retval_ptr = NULL; unsigned i; tobj = zend_object_store_get_object(this TSRMLS_CC); zf = zend_get_std_object_handlers()->get_constructor(this TSRMLS_CC); this_ce = Z_OBJCE_P(this); fci.size = sizeof(fci); fci.function_table = &this_ce->function_table; fci.object_ptr = this; /* fci.function_name = ; not necessary to bother */ fci.retval_ptr_ptr = &retval_ptr; fci.param_count = ZEND_NUM_ARGS(); fci.params = emalloc(fci.param_count * sizeof *fci.params); /* Or use _zend_get_parameters_array_ex instead of loop: */ for (i = 0; i < fci.param_count; i++) { fci.params[i] = (zval ) (zend_vm_stack_top(TSRMLS_C) - 1 - (fci.param_count - i)); } fci.object_ptr = this; fci.no_separation = 0; fci_cache.initialized = 1; fci_cache.called_scope = EG(current_execute_data)->called_scope; fci_cache.calling_scope = EG(current_execute_data)->current_scope; fci_cache.function_handler = zf; fci_cache.object_ptr = this; zend_call_function(&fci, &fci_cache TSRMLS_CC); if (!EG(exception) && tobj->constructed == 0) zend_throw_exception(NULL, “parent::construct() must be called in ” “the constructor.”, 0 TSRMLS_CC); efree(fci.params); zval_ptr_dtor(&retval_ptr); } static zend_function_entry ext_class_methods[] = { PHP_ME(testclass, construct, 0, ZEND_ACC_PUBLIC) ... {NULL, NULL, NULL, 0, 0} } ZEND_MODULE_STARTUP_D(testext) { zend_class_entry ce; memcpy(&object_handlers, zend_get_std_object_handlers(), sizeof object_handlers); object_handlers.get_constructor = get_constructor; object_handlers.clone_obj = NULL; INIT_CLASS_ENTRY(ce, “TestClass”, ext_class_methods); ce_ptr = zend_register_internal_class(&ce TSRMLS_CC); ce_ptr->create_object = ce_create_object; constr_wrapper_fun.type = ZEND_INTERNAL_FUNCTION; constr_wrapper_fun.common.function_name = “internal_construction_wrapper”; constr_wrapper_fun.common.scope = ce_ptr; constr_wrapper_fun.common.fn_flags = ZEND_ACC_PROTECTED; constr_wrapper_fun.common.prototype = NULL; constr_wrapper_fun.common.required_num_args = 0; constr_wrapper_fun.common.arg_info = NULL; #if PHP_VERSION_ID < 50399 /* moved to common.fn_flags with rev 303381 */ constr_wrapper_fun.common.pass_rest_by_reference = 0; constr_wrapper_fun.common.return_reference = 0; #endif constr_wrapper_fun.internal_function.handler = construction_wrapper; constr_wrapper_fun.internal_function.module = EG(current_module); return SUCCESS; } </code> Another option is to check on every internal method call whether the native structure has been properly initialized by the native constructor. Since most instance methods will need to fetch the object, this is a good opportunity to do the check. For instance, the cairo extension does this: <code c> static inline cairo_surface_object* cairo_surface_object_get(zval *zobj TSRMLS_DC) { cairo_surface_object *pobj = zend_object_store_get_object(zobj TSRMLS_CC); if (pobj->surface == NULL) { php_error(E_ERROR, “Internal surface object missing in %s wrapper, you must call parent::construct in extended classes”, Z_OBJCE_P(zobj)->name); } return pobj; } </code> This has two disadvantages relatively to the previous method: - It defers the check until an instance method is called, instead of immediately when the problem occurs (when the user-land constructor doesn't call the parent native constructor). - The check is made on every method call, instead of only once. However, this is by far a more popular approach, since it's simple and portable -- it uses only stable parts of the API. A variant of this strategy is to centralize the object state validation in the
get_method
handler and either throw a fatal error or return a method that throws an exception from the handler in case the object state is invalid. This makes it easier to fix current code without replacing the calls to zend_object_store_get_object
in every method implementation.
Finally, another option, certainly less complex but more limiting, is to make the superclass constructor final.
===== Iterators =====
TODO
===== Serialization callbacks =====
TODO
===== Object creation and destruction =====
Object creation involves these steps:
- Allocate and initialize the underlying data structure
- Store the object
- Build a reference to the object
- (optional) Call the constructor
Calling the constructor is uncommon internally because there are easier ways to initialize the object (calling a zend_function
is verbose). The initialization steps that are common to all the objects of a given type can be done in step 1. The initialization of a particular instance (which e.g. depends on some other data, the kind of data that would be passed to a constructor) can be done in a separate auxiliary C function. Every time an object is instantiated internally, the programmer must also call this function to do instance-specific initialization. A constructor is still necessary to properly support the new
operator, but this strategy does not imply duplication of code -- the internal implementation of the constructor may rely on the same auxiliary function.
==== Data allocation and initialization ====
In general, this part is completely domain dependent. The programmer may allocate and initialize an object however he wants.
However, zend standard objects (those with a class entry) rely on the class entry's create_object
handler. Typically, these have a data structure whose pointer can be passed to functions that expect zend_object*
. Hence, the typical class entry create_object
handler will look like test_create_object
in the example below:
<code c>
typedef struct test_object {
zend_object std;
/* more properties follow */
...
} test_object;
static zend_object_handlers object_handlers;
static zend_object_value test_create_object(zend_class_entry *class_type TSRMLS_DC)
{
zend_object_value zov;
test_object *tobj;
tobj = emalloc(sizeof *tobj);
zend_object_std_init((zend_object *) tobj, class_type TSRMLS_CC);
#if PHP_VERSION_ID < 50399zend_hash_copy(tobj->std.properties, &(class_type->default_properties), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*));#else
object_properties_init((zend_object*)tobj, class_type);#endif
/* The destroy and free callbacks should be replaced if necessary */ zov.handle = zend_objects_store_put(tobj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC); /* other specific stuff */ ... zov.handlers = &object_handlers; return zov;} ZEND_MODULE_STARTUP_D(testext) {
zend_class_entry ce; zend_class_entry *ce_ptr;
memcpy(&object_handlers, zend_get_std_object_handlers(), sizeof object_handlers); /* replace necessary handlers */ ...
INIT_CLASS_ENTRY(ce, "TestClass", ext_class_methods); ce_ptr = zend_register_internal_class(&ce TSRMLS_CC); ce_ptr->create_object = ce_create_object; /* Other startup stuff */ ...
return SUCCESS;} </code> The
create_object
handler can also be NULL
, in which case the general operations listed in `test_create_object` are executed except a vanilla zend_object
structure is initialized (instead of a test_object
).
==== Object storage ====
Objects are accessed through their references, the only thing linking the references to object instances is a integer (the object handle). This handle is a key that allows access to the object data structure. How this is done depends entirely on the type of the object.
Of particular relevance, are, of course, zend standard objects. These are stored in the 'objects store
'. The zend objects API exposes these functions:
/* Storage */ typedef void (*zend_objects_store_dtor_t)(void *object, zend_object_handle handle TSRMLS_DC); typedef void (*zend_objects_free_object_storage_t)(void *object TSRMLS_DC); typedef void (*zend_objects_store_clone_t)(void *object, void **object_clone TSRMLS_DC); zend_object_handle zend_objects_store_put(void *object, zend_objects_store_dtor_t dtor, zend_objects_free_object_storage_t storage, zend_objects_store_clone_t clone TSRMLS_DC); /* Retrieval */ void *zend_object_store_get_object(const zval *object TSRMLS_DC); void *zend_object_store_get_object_by_handle(zend_object_handle handle TSRMLS_DC); /* refcount related */ void zend_objects_store_add_ref(zval *object TSRMLS_DC); void zend_objects_store_del_ref(zval *object TSRMLS_DC); void zend_objects_store_add_ref_by_handle(zend_object_handle handle TSRMLS_DC); oid zend_objects_store_del_ref_by_handle_ex(zend_object_handle handle, const zend_object_handlers *handlers TSRMLS_DC); void zend_objects_store_del_ref_by_handle(zend_object_handle handle TSRMLS_DC); zend_uint zend_objects_store_get_refcount(zval *object TSRMLS_DC); /* Misc */ zend_object_value zend_objects_store_clone_obj(zval *object TSRMLS_DC); /* zend_object_store_set_object: * It is ONLY valid to call this function from within the constructor of an * overloaded object. Its purpose is to set the object pointer for the object * when you can't possibly know its value until you have parsed the arguments * from the constructor function. You MUST NOT use this function for any other * weird games, or call it at any other time after the object is constructed. * (This is rarely used) */ void zend_object_store_set_object(zval *zobject, void *object TSRMLS_DC); /* Called when the constructor was terminated by an exception. Prevents the * "destroy object" store callback from being called */ void zend_object_store_ctor_failed(zval *zobject TSRMLS_DC); /* Used to destroy all the objects in the store */ void zend_objects_store_free_object_storage(zend_objects_store *objects TSRMLS_DC);The objects store can actually store any type of data structures; the data structure doesn't have to be an extension of
zend_object
. The header file zend_objects.h
provides some functions to deal exclusively with zend standard objects:
/* To be used in the create_object class entry handler to initialize the * zend_object structure */ void zend_object_std_init(zend_object *object, zend_class_entry *ce TSRMLS_DC); /* Despite the name, this is actually related to object freeing. It frees all * the memory used by the inner structures of zend_object */ void zend_object_std_dtor(zend_object *object TSRMLS_DC); /* The default implementation of the create_object handler */ zend_object_value zend_objects_new(zend_object **object, zend_class_entry *class_type TSRMLS_DC); /* The default implementation of the free object store callback. Calls * the PHP destructor, if any. */ void zend_objects_destroy_object(zend_object *object, zend_object_handle handle TSRMLS_DC); /* Alias of zend_object_store_get_object, except it returns a zend_object * pointer instead of void* */ zend_object *zend_objects_get_address(const zval *object TSRMLS_DC); /* Copies the properties of the old_object and calls the class entry * clone handler. Used in the implementation of zend_objects_clone_obj * In PHP > 5.3, it also initializes the properties before. */ void zend_objects_clone_members(zend_object *new_object, zend_object_value new_obj_val, zend_object *old_object, zend_object_handle handle TSRMLS_DC); /* Allocates a new object with zend_objects_new and clones the members. * It's the default implementation of the clone object handler. The fact * it uses zend_objects_new means you almost certainly will want to replace * the clone object handler when implementing internal classes. */ zend_object_value zend_objects_clone_obj(zval *object TSRMLS_DC); /* default implementation of the free storage store callback. * Calls zend_object_std_dtor and then frees the object itself */ void zend_objects_free_object_storage(zend_object *object TSRMLS_DC);The function
zend_objects_store_put
adds an object to the store. This is the function that must be called during the creation of the object, as exemplified in the listing of the section before. All of the three last arguments may be NULL
.'Destructor
': IfNULL
,zend_objects_destroy_object
is used instead, which calls the PHP destructor, if any. This is called prior to the “free storage” callback when destroying the object. Cleanup of the memory allocated to the object data structures is left to the “free storage” callback. This callback is not called if the object construction failed. If passing a custom store destructor callback, calling the PHP destructor can be delegated tozend_objects_destroy_object
.'Free storage
': Used to free the object data structures. For vanilla zend objects, this should bezend_objects_free_object_storage
; if extending zend standard objects, in the custom callback one should delegate tozend_objects_free_object_storage
the cleanup of thezend_object
field and of the outer object data structure (hence, the call tozend_objects_free_object_storage
should be the last thing). There's no default ifNULL
is specified.'Clone
': Most likely, this should beNULL
. One should only use this callback if implementing objects without class entries and usingzend_objects_store_clone_obj
as aclone_obj
handler. Then, that function will call this callback, which should allocate a new object, use the passed double indirection pointer to store a pointer to it, and clone the passed object into this new one.
zend_objects_store_put
, the object will have reference count = 1 in the store.
==== Object reference creation ====
If we're creating a zend standard object, the create_object
handler already returned a zend_object_value
. The creation of an object reference zval is handled automatically by the new
operator.
To instantiate new objects internally, the following macros are available:
int object_init(zval *arg); int object_init_ex(zval *arg, zend_class_entry *ce); /* This function requires 'properties' to contain all props declared in the * class and all props being public. If only a subset is given or the class * has protected members then you need to merge the properties separately by * calling zend_merge_properties(). */ int object_and_properties_init(zval *arg, zend_class_entry *ce, HashTable *properties); /* This function should be called after the constructor has been called * because it may call __set from the uninitialized object otherwise. */ void zend_merge_properties(zval *obj, HashTable *properties, int destroy_ht TSRMLS_DC);These all take an allocated and initialized (
INIT_ZVAL
) or partially initialized (INIT_PZVAL
) zval
pointer. object_init
is not particularly useful, since it will instantiate a stdClass
object. object_and_properties_init
also allows efficient initialization of the object properties, but it has the limitations indicated in the comments. If all instances are to be initialized with the same property values, the default property values, defined when the class is registered, should be used instead.
==== Flow of the construction/destruction process ====
This is an overview of the process of object construction for zend standard object derivatives.
For internal instantiations:- Allocate and (partially) initialize a
zval *
. - Call
object_init_ex
. A pointer to the class entry should be available.- Call the class entry's
create_object
handler.- Allocate the object structure. This structure's first field should be a
zend_object
. - Call
zend_object_std_init
to initialize thezend_object
part of the object data. - Copy the default properties from class entry into the properties hash table of the new object. In PHP >= 5.3.99,
object_properties_init
should be called instead because non-dynamic properties are stored in C arrays instead of the properties hash table (though the hash table is still used when it's requested or when there are dynamic properties). - Call
zend_objects_store_put
, passing a custom “destroy object” callback which does cleanup specific to properly constructed objects and a custom “destroy object” callback that frees all the memory and other resources taken by the object (which is always called). - Assign the return value of
zend_objects_store_put
to thezend_object_value
that is to be returned. - Set the field
handlers
ofzend_object_value
that's to be returned to the appropriate object handlers table.
- Set the zval type to
IS_OBJECT
and the value to that returned by thecreate_object
handler.
- Do post-creation initialization on the new objected (the construction phase), typically through an auxiliary function.
new
:- Call the class entry's
create_object
handler.- (see above)
- ...
- Call the PHP constructor, if any. Typically, the internal implementation of the constructor delegates the construction task to the same auxiliary function referred to in the last step of the list before.
add_ref
and del_ref
handlers.