Search code examples
phpc++objectphp-extensionphp-internals

Call object constructor (__construct) from a php extension


I'm just experimenting with a PHP extension and I would like to know what is the suggested/preferred way to call an object constructor within the extension. I've read that, by calling the object_init_ex function the constructor of that object is not automatically called. This seems true also from the tests I've made. Let us say that I have the following code where 'Person' is a valid class name:

zend_class_entry *class_entry = NULL;  
zend_string *class_name = zend_string_init("Person", sizeof("Person") - 1, false);

class_entry = zend_lookup_class(class_name);

if (class_entry != NULL) {
  object_init_ex(return_value, class_entry);    
  /* call the Person::_construct method */
} else {
  RETURN_NULL();
}  

How can I call the constructor after the object_init_ext call? Also, will there be any differences between php 5 and php 7 in this regard?


Solution

  • There's two things you need to do: Fetch the constructor and then call it. The first part is easily done: You simply need to call the get_constructor() handler of the object:

    zend_function *ctor = Z_OBJ_HT_P(obj)->get_constructor(Z_OBJ_P(obj));
    

    Next, you need to call this function. Interestingly, there is no easy way to do this in the PHP API, because the usual call helpers deal with calling something by name (or by callable), rather than calling a function pointer directly. This means that you're required to manually initialize an fcall_info entry and fcall_info_cache. I'll provide a general purpose function here:

    int call_function_by_ptr(zend_function *fbc, zend_object *obj, zval *retval, uint32_t num_params, zval *params) {
        zend_fcall_info fci;
        zend_fcall_info_cache fcc;
    
        fci.size = sizeof(fci);
        fci.object = obj;
        fci.retval = retval;
        fci.param_count = num_params;
        fci.params = params;
        fci.no_separation = 1;          // Don't allow creating references into params
        ZVAL_UNDEF(&fci.function_name); // Unused if fcc is provided
    
        fcc.initialized = 1;
        fcc.function_handler = fbc;
        fcc.calling_scope = NULL;       // Appears to be dead
        fcc.called_scope = obj ? obj->ce : fbc->common.scope;
        fcc.object = obj;
    
        return zend_call_function(&fci, &fcc);
    }
    

    Assuming your constructor has no arguments, the actual call would then look something like this:

    zval retval;
    int result = call_function_by_ptr(ctor, Z_OBJ_P(obj), &retval, 0, NULL);
    if (result == FAILURE || Z_ISUNDEF(retval)) {
        // Error
    } else {
        // Success
    }
    zval_ptr_dtor(&retval);