3

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?

Machavity
  • 30,841
  • 27
  • 92
  • 100
  • 1
    @Mike The duplicate is about pure PHP, this question is about PHP extensions, written in C. – Charlotte Dunois Sep 06 '16 at 21:01
  • @CharlotteDunois Good point. I've retracted my vote. – Mike Sep 06 '16 at 21:07
  • It's too late for me to write this as an answer - but I think you want to duplicate the functionality in ReflectionClass::newInstance - https://php-lxr.adamharvey.name/source/xref/master/ext/reflection/php_reflection.c#4816 – Danack Sep 06 '16 at 21:28
  • Oh, and this is where having constructors be callables, would be rather useful *grumble* *grumble*. – Danack Sep 06 '16 at 21:29
  • 1
    @Danack Or rather, what would be usable is a helper that directly calls a zend_function without requiring manual fci/fcc setup. It's weird that doing a call when you already know *exactly* what needs to be called is harder than doing a callable lookup... – NikiC Sep 06 '16 at 22:38

1 Answers1

5

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);
NikiC
  • 100,734
  • 37
  • 191
  • 225