Cover V11, I10

Article

oct2002.tar

Using Oracle with Apache and PHP on Linux

William W. David

At a USENIX conference several years ago, Linus Torvalds was asked about the scarcity of database software for Linux. The problem, he answered, was that while operating systems are fun, databases are so ugly that you always have to pay someone to write them. MySQL and Postgres have proved that notion not entirely true, and until fairly recently, free database products were the only real option for database software on Linux. But now there are a number of commercial database products officially supported on Linux, including industry heavyweights Oracle and DB2. Those products, combined with the Apache Web server and the PHP programming language, make Linux a very attractive platform for developing and running Web-enabled database applications of all sizes. This article outlines the steps needed to access Oracle databases from PHP running under Apache.

Platform Notes

A development version of Oracle 9i is available at: http://www.oracle.com/ (The download requires registration.) PHP and Apache are available at: http://www.php.net/ and http://www.apache.org/.

Oracle 9i installs with only minor difficulties under the current versions of both SuSE and Red Hat Linux, and probably under other distributions as well. Oracle 8i and older distributions of Linux can also work, albeit with effort. An Oracle installation tutorial is beyond the scope of this article, but there are several helpful sites around the Web, including dbdomain: http://www.dbdomain.com/articles.htm/.

The examples in this article were written using Red Hat 7.3, Apache 1.3.23, PHP 4.1.2, and Oracle 9i (9.0.1.0.0.) They assume basic familiarity with the Apache and PHP build processes, Oracle in general, and a functioning Oracle database somewhere on your network. The examples use the sample database and users provided by the default Oracle installation.

Adding Oracle Support to Apache and PHP

There are several ways for PHP to talk to an Oracle database including using ODBC libraries, as well as two types of Oracle library support built into PHP. This article concentrates on using the Oracle native libraries -- specifically the Oracle 8 function calls. The older Oracle libraries still exist in PHP, but are largely deprecated by the OCI8 functions.

Before you can compile PHP with Oracle support, the computer doing the build must have Oracle network connectivity established. If the machine on which you're building Apache and PHP also hosts the Oracle database itself, you should already have everything you need. Otherwise, run the Oracle setup utility and install the Oracle client software. Test the installation by making sure you can connect to your database with sqlplus and log in with the one of the sample accounts Oracle provides (i.e., user name "scott"; password "tiger").

Once the Oracle client software is installed, it's time to add Oracle support to PHP. This is done using the --with-oci8= option to the PHP configuration script. The option should point to the location of the database client software (your $ORACLE_HOME directory, in Oracle terms).

The example PHP config.nice file used for this article looks like:

#! /bin/sh
#
# Created by configure

'./configure' \
'--with-apache=../apache_1.3.23' \
'--with-oci8=/db/oracle/product/901' \
'--enable-sigchild' \
"$@"
The --enable-sigchild line obviates a problem some users on the Internet have reported. The problem is characterized by a large number of defunct processes existing when using the Apache/PHP/Oracle combination under Linux.

After you've configured PHP and installed it, build Apache with PHP support enabled. Oracle support also requires that Apache be compiled with the pthread library included. The Apache configuration file with the minimal information for PHP and Oracle is very small:

#!/bin/sh
##
##  config.status -- APACI auto-generated configuration restore script
##
##  Use this shell script to re-run the APACI configure script for
##  restoring your configuration. Additional parameters can be supplied.
##

LIBS="-lpthread" \
./configure \
"--with-layout=Apache" \
"--prefix=/www/apache" \
"--activate-module=src/modules/php4/libphp4.a" \
"$@"
A real-world Apache configuration file will likely have many more options enabled in addition to those shown here.

For Oracle to work under Apache, the Web server needs to know a few things about the database environment. This is be done by adding several environment variables to the Apache startup script. These variables are the same ones that are typically set for normal Oracle users. You can add them to the configuration section of the apachectl script, which is located in the Apache installation's /bindirectory:

# ||||||||||||||||||| START CONFIGURATION SECTION  ||||||||||||||||||||
# --------------------                             --------------------
export ORACLE_SID=example
export ORACLE_BASE=/db/oracle
export ORACLE_HOME=/db/oracle/product/901
export ORA_NLS33=/db/oracle/product/901/ocommon/nls/admin/data
export PATH=$PATH:$ORACLE_HOME/bin:
An alternative, and probably more globally useful, solution is to keep them in an external place for all users and sourced where appropriate (from etc/profile, apachectl, other places as needed.) On the example system, they are in /db/dbparms, so the apachectl file would look like:

# ||||||||||||||||||| START CONFIGURATION SECTION  ||||||||||||||||||||
# --------------------                             --------------------
. /db/dbparms
After Apache is built and running, it's time to talk to the database.

Oracle Functions

There are several basic steps to running any Oracle SQL command under PHP, which include connecting to the database, parsing the SQL command, executing the command, and returning any results. Connecting to the database is accomplished using the OCILogon command. The command takes three arguments: database user name, password, and the name of the database itself:

$db_conn = ocilogon("scott", "tiger", "example.mydomain.net");
The database name parameter is optional, and if it isn't specified, the value of $ORACLE_SID is assumed.

Once you've connected to a database, the next thing is to run some SQL commands, which is accomplished with a combination of functions -- OCIParse and OCIExecute to generate results, and one of several statements to return the results in a usable form. For this example, I've used OCIFetchStatement, which puts the results of a query into an array, and OCIRowCount, which returns the number of affected rows from an update. The basic syntax of these commands is:

int OCIParse -- int conn, string query

int OCIExecute -- int stmt_id, int mode

int OCIFetchStatement -- int stmt_id, array variable

int OCIRowCount -- int stmt_idd

OCIParse takes as arguments the database connection generated in the previous example and an SQL string, and returns the statement ID of the parsed statement. OCIExecute takes the results of OCIParse and an optional mode, which tells the database whether to commit the results of the command (the default is to commit) and returns TRUE on success, FALSE on failure. OCIFetchStatement takes the statement ID of an executed statement and an array to populate with the results of the query, and returns the number of rows in the result set. OCIRowCount takes a statement ID and returns the number of rows altered by an update.

Here's an example showing all these commands in use. It updates an employee record in the sample database, and then queries for some information about employees named Smith and Jones:

<?
$db_conn = ocilogon("scott", "tiger", "example.mydomain.net");

$cmdstr = "update emp set job = 'techie' where ename = 'SMITH'";
$parsed = ociparse($db_conn, $cmdstr);
ociexecute($parsed);
$nrows = ocirowcount($parsed);
echo "$nrows row updated.<BR>\n";

$cmdstr = "select ename, job from emp where ename in ('SMITH', 'JONES')";
$parsed = ociparse($db_conn, $cmdstr);
ociexecute($parsed);
$nrows = ocifetchstatement($parsed, $results);
echo "Found: $nrows results<BR>\n";

echo "<TABLE BORDER=1>\n";
echo "<TR>\n";
print "<TH>Employee Name</TH>\n";
print "<TH>Job Title</TH>\n";
echo "</TR>\n";

for ($i = 0; $i < $nrows; $i++ ) {
  print "<TR>\n";
  echo "<TD>" . $results["ENAME"][$i] . "</TD>";
  echo "<TD>" . $results["JOB"][$i] . "</TD>";
  print "</TR>\n";
}

print "</TABLE>\n";
?>
The example should produce output similar to the following:

1 row updated.
Found: 2 results

Employee Name  | Job Title
--------------------------
SMITH          | techie
JONES          | MANAGER
Everyday Use
I've described how to get PHP and Apache to talk to a database, but the steps above are a little unwieldy for everyday use. Consider a typical Web site, with hundreds of pages (perhaps written by a team of programmers), with several queries each -- all writing the connect, parse, execute, and return sequences for each SQL statement. It's a good idea to abstract all the pieces that can be hidden into another layer to provide one well-known interface that all pages use, with consistent options and return values. This also makes it easy to take advantage of another feature -- persistent database connections. A persistent connection is set up once and reused for the entire PHP session, eliminating the overhead of repeatedly connecting to the database.

To that end, we put the following in an include file:

<?
function dbconnect()
{
   global $db_conn;

   $db_conn = ociplogon("scott", "tiger", "example.mydomain.net");
}


function perform_sql($cmdstr, &$results)
{

   global $db_conn;

   if (!$db_conn) {
      dbconnect();
   }

   $parsed = ociparse($db_conn, $cmdstr);
   ociexecute($parsed);

   if (func_num_args() == 2) {
      $nrows = ocifetchstatement($parsed, $results);
   }
   else {
      $nrows = ocirowcount($parsed);
   }

   return($nrows);
}
?>
and our example above becomes:

<?
include "dbutils.php";

$cmdstr = "update emp set job = 'techie' where ename = 'SMITH'";
$nrows = @perform_sql($cmdstr);
echo "$nrows row updated.<BR>\n";

$cmdstr = "select ename, job from emp where ename in ('SMITH', 'JONES')";
$nrows = perform_sql($cmdstr, $results);
echo "Found: $nrows results<BR>\n";


echo "<TABLE BORDER=1>\n";
echo "<TR>\n";
print "<TH>Employee Name</TH>\n";
print "<TH>Job Title</TH>\n";
echo "</TR>\n";

for ($i = 0; $i < $nrows; $i++ ) {
   print "<TR>\n";
   echo "<TD>" . $results["ENAME"][$i] . "</TD>";
   echo "<TD>" . $results["JOB"][$i] . "</TD>";
   print "</TR>\n";
}

print "</TABLE>\n";
?>
The sections of code that perform the database activity are more compact in the example, down to four lines from nine, but the HTML section is as ugly as ever. This savings becomes magnified on a large Web page containing many database calls. Notice the use of OCIPLogon in place of the original OCILogon, denoting a persistent connection. Also important is the consistency of the new interface. The same command, perform_sql(), is used for both update and select statements, rather than having two different ones (OCINumRows and OCIFetchInto), as in the original example.

Moving the OCI8-specific pieces out of the main page code also allows for some less obvious trickery. For example, to ensure that all string operations on the database have newlines removed, you might change the appropriate section of dbutils.php:

$cmdstr = ereg_replace("\n", "", $cmdstr);
$parsed = ociparse($db_conn, $cmdstr);
ociexecute($parsed);
Making the change here means the Web code is only changed in one place, and that the change applies to everybody. The same thing could be done for other characters (i.e., to eliminate dashes from social security numbers), to format date stamps, and so forth.

Summary

This article shows how to use Apache and PHP to access an Oracle database. All three are very flexible tools, and in practice the combination is quite pleasant to use. PHP can also be used as scripting language for accessing Oracle outside of a Web context. It has become my language of choice for database programs, whether Web-based, command line, or batch. With a little trial and error, it might be yours, too.

William W. David is a long-time UNIX and network administrator as well as sometime DBA and programmer. He's spent the past few years working with Web-enabled databases on the Internet, and is afraid they're here to stay. He can be reached at: wdavid@cowford.net.