Posted by: camz | August 15, 2005

Dynamic Loading in PHP


I recently started on a small project in PHP, and in working on the project I began to explore how to leverage the OO capabilities of PHP. Much to my delight, I discovered that PHP is a far more capable language than its normally given credit for. I’m sure that there are just as many reasons for that as there are opinions, so I won’t share my opinions (this time). Instead, I would like to share a technique that I have recently developed since I feel that it will not only be interesting to the PHP community, but useful as well.

The technique is a form of dynamic loading, the PHP equivalent of a DLL (more or less). At first, this seems like a rather strange concept for an interpreted language, but it has uses, the most important being that it can reduce code complexity without sacrificing functionality. It is based on the OO capabilities of PHP, and as such is an abstraction layer, which I have blogged about in the past. As with all abstractions, it is always important to understand the internals of the abstraction instead of just blindly using it. With that warning issued, lets get into the actual technique.

In general terms, I needed a “driver” for my project, so I will use the driver model as the basis for this article. Many years ago I stumbled across a driver technique that was used in gnuplot. It was a clever abstraction of the driver layer, even though it was written in C (none of those fancy OO languages existed, and those that did, were not well known and had not gained any real acceptance). This was (and still is) a fine example of how OO-design is a methodology and does not require an OO language to implement.

A little history from C
The trick that gnuplot used was to define a structure that contained some data elements and some pointers to functions. If this sounds an aweful lot like a modern class definition, you’re right, it’s virtually the same thing. The functions implemented the lowest level of functionality in gnuplot, and there were high-level functions that called these “driver” functions to implement more complex functionality. Here is what some of the code looked like:


typedef struct Driver {
char name[31],
void *(plot)( int, int ),
void *(pen)( int ),
void *(move)( int, int ),
void *(line)(int, int )
} DEVICE_DRIVER;

Each driver in gnuplot would provide the functions for implementing these basic features, and a global module provided an array of them along with the appropriate initialization. This could also have been implemented using a DLL-type technique, but again, those techniques, let alone DLL’s were not in common use at the time (there I go dating myself). Using this model was quite simple, a global variable existed which was a pointer to the current driver within the array. Here is what a call to one of the functions would look like:


#define PEN_DOWN 1
#define PEN_UP 0

(*driver->pen)( PEN_UP );
(*driver->move)( 0, 0 );
(*driver->pen)( PEN_DOWN );
(*driver->line)( 0, 100 );
(*driver->line)( 100, 100 );
(*driver->line)( 100, 0 );
(*driver->line)( 0, 0 );

When the user selected the output driver, it was a simple matter to loop through the array looking for a name match and then setting the global pointer appropriately.

It was simple and elegant, and most importantly, it provided a layer of abstraction for the higher-level functions so that the device-specific code stayed in one place and there were no #ifdef/#endif commented sections in the main code.

Time for PHP
I wanted to do the same thing with PHP. The first challenge was that PHP doesn’t have structures, if you want to have a structure you have to use an object, which in turn means defining a class. This was actually a good thing, since classes also support methods, which work perfectly as a replacement for the pointers to functions that were used in C. I started off by defining a base class, for our example, I’ll use a subset of database functions.


class DB_DLL {
var $name;
var $db_link;

function DB_DLL() { $this->name = null; }
function init( $db_host, $db_user, $db_pass );
function connect( $db_name ) { return null; };
function query( $db_query ) { return null; };
}

Those of you that didn’t skip over the C part on gnuplot will notice that this looks remarkably similiar, almost identical to the C technique. It should we are doing the same thing. The only difference lies in how we initialize things.

In C we had to have a global array of structures and each was hard-coded to initialize it in order for it to be usable to the application. The other big difference is that in C each driver actually did populate the same data type with it’s information, so we had an array of the same data structures. Things don’t quite work that way in PHP. In order to provide our own functionality for each “driver”, we must extend the base class, which creates a new data type, a new object. This also creates a new problem, but we will get to that later. Here is one example of the code to extend the base class for MySQL.


class DB_MYSQL extends DB_DLL {

function DB_MYSQL(
{
$this->name = “MySQL”;
}
function init(
{
$this->$db_link = @mysql_connect( $db_host, $db_user, $db_pass );
}

function connect( $db_name )
{
return @mysql_db_select( $db_name );
}

function query( $db_query )
{
return @mysql_query( $db_query );
}

Clever tricks
Okay, so now we have a base class and some extended classes that actually implement our “drivers” for (in this case) various database types. We still have a problem, which is how to instantiate an object of an extended class. They all have new names, so how do we manage this?

First we need to organize things a bit, so I’ll have to backtrack a little to do this. The organization is in terms of how we name files and where we put them. So the first thing we will do, is to place the definition of our base class into it’s own file so that we can include it in any PHP module that needs it. For now, lets call it class.inc, and of course it will need the standard wrapper tags. Next we will put the definition of the extended classes into their own files as well. Although I have only shown a sample of the code for MySQL, lets say that we’ve created modules for PostgreSQL, and Oracle as well. To keep things even tidier, lets put them all in a subdirectory called drivers. We should now have the following files:


class.inc
drivers/mysql.inc
drivers/postgresql.inc
drivers/oracle.inc

Now, back to solving our little problem of instantiation. Turns out this is an easier problem than you might think. PHP has the ability to call a named function, simply by putting the name of the function as a string into a variable and then calling it by adding () after it. So we now go back to each driver file that we created and we add a function outside of the class definition. The one for MySQL looks like this:


function new_db_dll_mysql() { return new DB_MYSQL() }

Okay, looks pretty simple. It is, that’s the beauty of it. We have one more trick though, and this where things get more interesting. We still need to make this all useful, and to do that we will instantiate each driver and build an array. Since these are all extended from our base class, the array is just an array of the base class, with each element instantiated to the appropriate extended class. We take advantage of calling a named function to do this. Here is what our main code looks like:


global $DLL;
require_once( “class.inc” );

$dll_list = array( “mysql”, “postgresql”, “oracle” );
foreach( $dll_list as $driver ) {
require_once( “drivers/$driver.inc” );
$init_func = “new_db_dll_$driver”;
$DLL[$driver] = $init_func();
}

We created an array to hold the list of drivers we wanted to initialize / instantiate, and then used a simple loop to do all the work. Using any of the function is pretty simple too, here is an example:


$driver = “mysql";

DLL[$driver]->init( “localhost”, “userid”, “passwd” );
DLL[$driver]->connect( “my_db” );
$result = DLL[$driver]->query( “SELECT * FROM blog_entry WHERE id = 5″ );

Once very important thing to notice about the code that initialized the drivers is that it does not get longer or more complex as we add more drivers. The only thing that changes is the array. This may seem quite trivial, but it is actually where some of the more powerful functionality is hiding. I will also admit that my choice of using database functions for abstraction with this technique probably makes it less obvious as to the power of this technique.

To make this more apparent, think about using this technique something other than a database. Lets say we have written drivers that create different pages on a web site. We might have one for forums, and one for downloads, and yet another for administration functions. Another might be a set of functions for connecting to different instant messenger web-interfaces.

Instead of putting the list of these driver modules into an array, lets put them into a database. We can now take a userid for the logged in user and build a query that will provide us with a list of modules that this particular user has access to. That list can then be used to dynamically initialize/instantiate only those modules that the user has access to.

We’ve just transformed a simple technique for creating drivers into a mechanism for dynamically including selected features into a web-application!

I should probably make one comment about the technique before anyone else points it out. There is a slight simplification that can be made to the technique if you use include_once() instead of require_once(). The trick is that an included file can return a value. So instead of having an extra function at the end of the extended class definition you could just have:


return new DB_MYSQL();

With the initialization loop now looking like this:


foreach( $dll_list as driver ) {
$DLL[$driver] = include_once( “drivers/$driver.inc” );
}

Which reduces the initialization code by a couple more lines.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: