By Sterling Hughes PHP is a valuable item in any Web developer's toolbox. The server-side technology allows for rapid development of dynamic, database-driven applications. One of PHP's major strengths is its ability to connect with many different databases, including Sybase, PostgreSQL, Oracle, or MySQL. Database connectivity, however, is also one of PHP's major weaknesses. Specifically, PHP's lack of a standard database API makes switching databases difficult and costly once an application has been written. For example, if you write a program that uses a MySQL database on the back end, and you want to modify that program so that it uses a Sybase database, you'll most likely spend hours modifying the code. Highest Form of Flattery Fortunately, there are several workarounds to this problem, including a couple that rely on new features in PHP4. A lot can be learned from examining (and borrowing) a solution from Perl. For the particular solution presented here, I've used Perl's DBI as a model. Before Perl 5, many Perl programmers faced the same problem that PHP programmers face today. They had to use different modules and subroutines to access different databases. For example, the sybperl module worked with Sybase, while oraperl interfaced with Oracle. Switching databases meant going through the code and changing all references from sybperl to oraperl. Perl programmers got frustrated and began looking for a better solution. The result was the DBI module. DBI lets you access many different databases with the same set of methods. For example, the code in Listing 1 checks a MySQL database to verify a username and password pair. If you wanted to change the database from MySQL to Sybase all you'd need to do is change the value of the $dbtype variable from mysql to sybase. Listing 2 illustrates the same script written in PHP4. Notice that you need to make function calls that are specific to the MySQL database, such as the mysql_connect function. If you want to change the database you have to change all function calls (that is, mysql_connect to sybase_connect). While this might not be too bad for a small project, it creates a problem for large programs with many database calls. Notice that the calls are very similar though. When you're working with a Sybase database you use sybase_connect. When you're working with an Interbase database, you use ibase_connect. A MySQL database uses mysql_connect. An mSQL database uses msql_connect, and so on. And if you look at all the different functions for every database, they may vary a little but the basic idea is the same: Each database has its own version of the connect, query, fetch, free, and close functions. Because of these similarities, it's possible to write a wrapper around these functions and create a database-independent API. The Plan There are several ways to incorporate a database-independent API into your applications. One is to use the PHPLIB library. The trouble with PHPLIB is that it's not a good solution for writing portable applications. While PHPLIB is popular on many systems, for every person who uses PHPLIB there are many who don't (or can't). If you write an application that requires PHPLIB, you have to be sure the library is available on the target system. Often, you won't have that luxury. Another option is to use an object-oriented approach, much like Perl's DBI does. In this case, you'd create a class that you could instantiate (probably named something like DB). After you create the instance of the class, you'd supply, as a parameter to a function, the name of the specific database you wanted to use. The DB class would then call another class that handles the specific access to that type of database. An example of database independence through classes is available from phpclasses.upperdesign.com, via a class named Metabase. However, I'm not going to cover the object oriented approach because, as Zeev Suraski, CTO of Zend Technologies says, "PHP is not an object oriented language." While PHP version 4 does support the main object-oriented concepts, it wasn't meant to be a full-blown object-oriented programming language. Instead, let's look at two other approaches. The first is to use variables that are set at the beginning of the code, as in Listing 3. The $dbtype variable contains the name of the database you're using. Then, other variables and basic function calls refer to the $dbtype variable repeatedly. Building dynamic function calls in this way is a new feature found in PHP4. Previously, this method wasn't possible. The variable approach will work pretty well for several databases and is easy to implement. The main problem with the variable approach is that it's difficult to extend to complex examples and doesn't work with certain database connections (such as ODBC). Therefore, you may want to consider a function-based approach. I like the function-based approach best because it's fast, extensible, and easy to use. You use generic wrapper functions for the specific database calls. When you call these wrapper functions they call the appropriate database functions on your behalf. Listing 4 demonstrates the use of the function-based API. This program reproduces the authentication script that I previously wrote using the mysql_* function calls (in Listing 2). Instead of calling the database-specific mysql_connect function, I now call the generic db_connect function. If you want to change the active database from MySQL to Sybase, simply change line 6 of Listing 4 to: include_once ("DB/sybase.php"); With that single modification, you can successfully swap the database from MySQL to Sybase. Creating Functions Designing an API is one thinghowever, implementing it is a completely different ball game. For the database swap in Listing 4 to work, you have to implement all the appropriate database functions in a file called sybase.php. This means putting together all the generic db_* functions you'll ever need, which is less difficult than it sounds. Consider the db_connect function call. It's simply a wrapper for the sybase_connect function. That's all you need to create the different functions: You simply have to delegate the generic functions like db_connect to the appropriate native call. Listing 5 shows some of the generic functions for a mysql.php file. Take a look at the full code. Because all the functions follow the same skeleton, I've created the dummy function in Example 1 for teaching purposes. Line 1 declares the function, the $args array allows for the passing of optional parameters. All of the generic functions begin with db_. The generic functions can take between zero and two parameters, and the switch statements in lines 3 through 11 account for that. If the caller supplies the maximum number of parameters, the switch statement falls into the default clause (instead of a specific case clause). This allows the function to cope with other databases that may require more than two arguments. Also, note that the code doesn't use a break inside the switch statement. Each case returns a value, causing the function to terminate (without a break statement). Missing Features One of the problems with this API is that you may lose some functionality. Some databases don't support transactions. For example, if you go from Oracle to MySQL, you'll lose transaction support and may have to modify your code in more places than one. In some cases you might be forced to use the proprietary database function calls to implement the missing features. The solution will depend on how complex your needs are. For something simple like preparing a query and executing it, you can write generic code that emulates the preparation of a query. The db_prepare and db_execute functions in Listing 5 do that. For more advanced features (like transactions) it may be too much effort to roll your own emulation. Therefore, I usually have my code return an error if the caller requests an unsupported function. For example, consider the db_commit function. If the database supports transactions, this call commits any pending operations. For a database that doesn't support transactions, I'd write a function similar to that in Example 2, where ERROR_NOT_CAPABLE is a predefined constant. In Practice The good thing about keeping your generic functions together in one file (per database) is that you can reuse the code in many projects. As you write more wrappers, you increase the number of databases you can use in any project. Soon you'll have a library of database wrappers stored in files like mysql.php, sybase.php, oracle.php. If you're like me, you'll usually want to separate the files from your application. That means that while your application might reside in the /www/htdocs directory, your included files might be in a directory like /usr/local/include. It's annoying always to have to specify the full path in your code as follows: include_once ("/usr/local/include/DB/mysql.php"); Fortunately, the php.ini file has a parameter called include_path just for this situation. This parameter tells PHP where to look for include files. If you change its value to /usr/local/include, you don't have to type the entire path in your code. Do note that local files have priority. If you have a file named SomeClass.php in both a directory specified by the include_path and also in your local directory, PHP will use the file in your local directory. In Conclusion When it comes to interfacing with a single database, PHP is an excellent tool. However, when you're writing a program that must run on many different database systems, PHP doesn't offer much aid. Libraries like PHPLIB can help in certain situations, but aren't completely portable solutions. Many people predict that PHPLIB will fade away because PHP4 incorporates many of its popular features, like sessions. One thing to look out for is the DBI class for PHP, which is under development and will be ready for prime time next year. Until then, using the variable- and function-based approaches is your best bet for writing programs that require database independence. (Get the source code for this article here.) Sterling is the author of PHP Developer's Encyclopedia (MacMillan). A Unix programmer, he's been a software developer for five years. Copyright 2003 CMP Media LLC