[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

Reply via email to