[This is quite long, so grab a cup/can/bottle/glass of your favourite beverage and get comfortable]
Following on from the recent talk about streams and filters, I've been thinking about how useful it would be for extension authors to have some kind of lightweight interface mechanism. I'm not talking about interface support in PHP scripts, I'm talking about a C API which acts as a thin layer over the top of the resource mechanism. I've come up with the API below; the implementation is deliberately very lightweight and should be very simple to actually put it to use. It might seem like there are holes in the API examples: thats because I haven't defined any interfaces yet, just the mechanisms on how they might work. The Concept: ============ Extension authors can and should be able to make use of resources that other extensions pass around. One example is that a domxml tree could be passed into the xslt extension for transformation. There are going to be other cases that crop up: passing images between different image extensions, or to PDF extensions or SWF generators etc. etc. To acheive this, the extension author will wrap the resources that their extension exports with a php_iface_instance: php_iface_instance { void *thisptr; /* extension/object specific data */ long rsrc_id; /* for exporting as a userland resource */ php_iface_instance_dtor dtor; /* for cleanup */ /* template describing the interfaces of this instance */ php_iface_template *templ; } php_iface_instance; Of this struct, the php_iface_template is the most important part: it behaves a bit like the zend_class_entry, in that it contains information about which interfaces are supported. The interface template can either be statically initialized or dynamically initialized using php_iface_create_template(). Using Interfaces ================ The C code for doing this would look something like this: void my_data_dtor(void *thisptr) { // free resources here } PHP_FUNCTION(create_new_thing) { my_extension_data *thisptr; // already filled in by code php_iface_instance *inst; ... inst = php_iface_create_instance(thisptr, /* see below for where this comes from */ my_outputter_object_template, my_data_dtor, le_my_extension); RETVAL_RESOURCE(inst->rsrc_id); } What this does is create the instance structure and then registers it as a resource in the specified resource list (le_my_extension). When a resource is passed back to the extension, there are two ways of handling it: the first is for when the resource is passed to the extension that created it: PHP_FUNCTION(use_thing) { php_iface_instance *inst; my_extension_data *thisptr; zval *zthing; ... zend_parse_parameters("r", &zthing) ... ZEND_FETCH_RESOURCE(zthing, php_iface_instance *, inst, -1, "my resource", le_my_extension); thisptr = inst->thisptr; ... use it as normal ... } The other way is when you are not necessarily the same extension that created the resource. This is where you actually use the interfaces. In this example, lets pretend there is an interface that outputs a string to some thing: it might be an image, a log or the browser output: typedef struct { int (*output)(void *thisptr, char *text, int textlen); } IOutput; Lets also assume that the interface has already been registered and has an ID of PHP_IFACE_ID_IOUTPUT. PHP_FUNCTION(output_something) { php_iface_instance *inst; my_extension_data *thisptr; zval *zthing; int type; void *what; char *string; int stringlen; ... zend_parse_parameters("rs", &zthing, &string, &stringlen) ... what = zend_fetch_resource(&zthing TSRMLS_CC, -1, "IOutput", &type, 2, le_my_extension, php_le_some_other_extension()); RETVAL_FALSE; if (php_iface_list_type_is_registered(type)) { /* its an interface compatible resource */ IOutput *outputter; inst = (php_iface_instance *)what; if (PHP_IFACE_AS(inst, PHP_IFACE_ID_IOUTPUT, &outputter)) { RETVAL_LONG(outputter->output(inst->thisptr, string, stringlen)); } else { zend_error(E_WARNING, "resource does not support IOutput"); } } else { zend_error(E_WARNING, "resource does not support interfaces"); } } Stock Interfaces and Dynamic Interfaces ======================================= In order to make interface querying a fast operation, we can identify them using an id number; if we use a long as a bitfield, we can very quickly test if an instance supports 1 of up to 32 interfaces. Thats fine if all interfaces are defined in the core (I call them Stock interfaces), but we should provide a means to identify interfaces exported by third-party extensions (I call them Dynamic). One way of achieving this is to identify a unique interface using a string; the string is registered at run-time and assigned an id. In the instance template, interface ids are mapped onto "v-table" pointers using a HashTable. In my test-code, I've combined both methods: by reserving bit 31 of the id number to indicate a dynamic interface, we still have the abililty to test/fetch Stock up to 31 stock interfaces very quickly, and then we can have 2^31-1 or so dynamic interfaces that can be tested/fetched as fast as an indexed HashTable will allow. Dynamic interfaces have a refcount, so that multiple extensions can register/unregister and otherwise refer to them without worrying about the interface dissappearing. I think that having a split mechanism like this is a little bit kludgy, but I anticipate that an argument against an interface mechanism is that is will cause a slow-down, especially for the commonly accessed interfaces, so this is idea is to try to compensate. Registering Interfaces and Resource Types ========================================= To make interfaces work, the extension author needs to perform a couple of extra steps in their MINIT functions: static php_iface_template *my_outputter_object_template, my_otherthingy_object_template; /* this is our implementation of IOutput::output. * the thisptr is whatever pointer we stick into the php_iface_instance * when we create it */ static int outputter_output(void *thisptr, char *text, int textlen) { printf(text); } /* a pointer to this will be returned when someone uses the * PHP_IFACE_AS() api on our objects */ static IOutput outputter_vtbl = { outputter_output }; #include "otherthingy.h" /* defines the otherthingy interface */ static IOtherThingy otherthingy_vtbl = { method1, method2 }; PHP_MINIT_FUNCTION(my_extension) { le_my_extension = zend_register_list_destructors(....) /* this indicates to other extensions that resources from this list * entry can be treated as an php_iface_instance pointer */ php_iface_register_list_type(le_my_extension); } /* now register an interface templates which will be used to create * instances of our extension specific objects. * Any dynamic interfaces will be registered here too. */ my_outputter_object_template = php_iface_create_template( 1, /* number of stock interfaces */ PHP_IFACE_ID_OUTPUT, &outputter_vtbl, 0 /* number of dynamic interfaces ); my_otherthingy_object_template = php_iface_create_template( 0, /* number of stock interfaces */ 1, /* number of dynamic interfaces */ "thebrainroom.com/otherthingy", &otherthingy_vtbl ); } PHP_MSHUTDOWN_FUNCTION(my_extension) { /* free the templates we created earlier. Any dynamic interfaces * will be unregistered too. */ php_iface_free_template(my_otherthingy_object_template); php_iface_free_template(my_outputter_object_template); /* unregister the list type as well */ php_iface_unregister_list_type(le_my_extension); } The object templates can be declared statically, provided that they don't include dynamic interfaces (because you can't statically initialize the contents of a HashTable). Using Dynamic Interfaces ======================== In an earlier example I showed how to use interfaces when you know the interface id. Typically, that will be the case when using Stock interfaces. When you are using dynamic interfaces, you have two options: You can lookup the interface id of the dynamic interface at the start of your code and cache the ID number and use the PHP_IFACE_AS() api as demonstrated above. Alternatively, if performance is not critical you can use the PHP_IFACE_AS_DYN() api: IOtherThingy *thingy; inst = (php_iface_instance *)what; if (PHP_IFACE_AS_DYN(inst, "thebrainroom.com/otherthingy", &thingy)) { thingy->method1(inst->thisptr); } Conclusion ========== I think that covers everything I can think of; as usual, all comments are welcomed! I have already coded the APIs described above; they are very lightweight, and if there are no objections, or comments to suggest that the approach needs altering, I'd like to commit them within the next week or so - to give people time to read and digest this RFC! --Wez. -- PHP Development Mailing List <http://www.php.net/> To unsubscribe, visit: http://www.php.net/unsub.php