Using Apache, PHP, and InterBase to Develop Database-driven Web Sites

By: Conference Speaker

Abstract: PHP and Apache allow one to easily develop Web sites that interact with InterBase. Topics include: configuring the system; login security; displaying standard data types; displaying BLOB data; and allowing the end user to insert, update, and delete data.

This is the technical paper from a talk given at the 11th Annual Inprise & Borland Developer's Conference
By Vince Duggan
Vince Duggan is the technical director of Synectics Software, an Inprise/Borland client/server business partner based in Sandton, South Africa. Over the past 15 years, he has worked with COBOL, BASIC, dBASE, Clipper, and Oracle on DOS, Windows, VMS, and UNIX platforms. Since late 1994, he has worked exclusively with Inprise/Borland products, particularly Delphi and InterBase.

Introduction
Installing Apache with PHP3 Support
Introduction to PHP programming
The Basics
A Word on Variable Scope
PHP3 Interbase API Reference
Connecting to Interbase
Interbase Transactions in PHP3
Executing Queries
Authenticating Users
Displaying the Data
Displaying Text Blobs
Displaying Binary Blobs
Updating Tables in the Database
The sample Application code

Introduction

There are three main players in the web server market: Apache, IIS, and Netscape. The top sites use these three about equally, although Apache dominates in the overall web server market. Of the Top Sites, about 70% use CGI programming. The three main scripting extensions used are ASP, Perl and PHP. The source of these stats is Nathan Wallace at www.e-gineer.com.

PHP3 and PHP4 have Interbase 5.x and Interbase 6 support respectively and allow easy development of Web sites which interact with Interbase.

Installing Apache with PHP3 Support

Most installations of Linux have the Apache package on the CD, and most default installs will install Apache. However, I suggest you remove that package, and then download the latest versions of Apache and PHP(3 or 4, depending on whether you need Interbase 5 or Interbase 6 support). Then follow the instructions found at Synectics Software to install them. Here is a very short list of the steps required. These steps assume that Apache has been downloaded and extracted into /usr/local/etc/httpd, and PHP has been put into /usr/local/etc/php. Furthermore, the Apache configuration files, and PHP's inititialization file go into /www/conf.

The latest Apache build can be found at Apache's ftp site while PHP can be downloaded from PHP's ftp site.

First configure the Apache makefiles:

   cd /usr/local/etc/httpd
   ./configure --prefix=/usr/local/etc/httpd --sysconfdir=/www/conf
Now make the executables: This can take quite a few minutes.
   make
Now install apache:
   make install

Now configure PHP3 with support for Apache and Interbase, then make and install the package:

   cd /usr/local/etc/php
   ./configure --with-apache=../httpd --with-interbase=/usr/interbase --with-config-file-path=/www/conf --enable-track-vars
   make
   make install

Now we need to rerun the Apache configuration program, this time with PHP3 support, run 'make' again, and 'make install' again:

The previous invocation of 'configure' created a script 'config.status' which can be run to re-configure Apache with the same options. We need to do that, as well as add a new option for PHP3 support, then make again, and install again:

   cd /usr/local/etc/httpd
   ./config.status --activate-module=src/modules/php3/libphp3.a
Now redo the make sequence:
   make
   make install

Now we need to put the default PHP3 INI file in the correct place:

   cd /usr/local/etc/php
   cp php3.ini-dist /www/conf/php3.ini
We need to configure the Apache httpd.conf file in order that Apache will pass certain files extensions to PHP3:
   cd /www/conf
Now edit httpd.conf, and uncomment the lines referring to php3:
   AddType application/x-httpd-php3 .php3
   AddType application/x-httpd-php3-source .phps
You can add other extensions if you like:
   AddType application/x-httpd-php3 .phtml

Apache and PHP are now installed. Apache needs a fair amount of configuring before you can build your web site. Go to Synectics Software for instructions on how to do this. There are also links to other sites which have much more detail.

Introduction to PHP programming

PHP3 and Interbase is remarkably easy to use. However the manner of programming CGI apps is quite a paradigm shift from ordinary programming in the Windows or Unix/Linux world. This little app that I am going to use to demonstrate some techniques is based on the standard EMPLOYEE.GDB database supplied with Interbase. I have added one field (PIC BLOB) to the employee table, which I populated with some jpeg images. A backup of this database is included in the supplied source code.

I also added a couple of tables to cater for the authentication (login) side of things, but these are explained as we go along.

In a nutshell, the app allows one to login to the database within your favourite browser, and then examine the details of those employees to whom you have been granted access. In this simple application, rights to view employees is granted on the departmental level.

There is also a page where the user is allowed to modify the salary of the employee being viewed. This is then updated in the database.

The Basics

When requesting a document from a web server with a file extension recognized by Apache as being a PHP3 document (usually .php3 or .phtml), Apache runs the document through the PHP3 module, and PHP3 decides what to present to the browser. If some or all of the contents of the documents is within the PHP3 start and end tags (<?php and ?>), then PHP3 will execute that portion as a PHP3 script. The rest of the document, usually ordinary HTML and/or other scripting languages like Javascript, is passed through to the browser.

So an example would look something like this:

   <HTML>
   <BODY>
   <P><H2><A NAME="basics">The Basics</A></H2>
   <P>When requesting a document from a web server etc etc etc

   <?php
   $SECTIONNO=6;
   include ("showcomments.php3");
   ?>

   <P><H2><A NAME="scope">A Word about Variable Scope</A></H2>
   <P>Like all programming languages etc etc etc

   <?php
   $SECTIONNO=7;
   include ("showcomments.php3");
   ?>

   </BODY>
   </HTML>
      

Within the PHP3 start and end tags, you have full access to the PHP3 scripting language. Most commonly one writes code here to access data, and then one generates HTML which displays this data. PHP3 has a function called 'echo', and any text which is echo'ed is sent to the browser. Typically one echoes HTML or plain text. Here is an example:

   <?php
     $MyColor="red";
     echo "<FONT color=$MyColor>This is written in RED</FONT>"; 
   ?>
This displays in the browser like this:

This is written in RED

You will see that I had a variable called $MyColor (all variables start with $) which contains the value of the color I want to use. This could of course have been looked up in a database. PHP3 then substitutes the value of the variable whenever required.

If you look at the page source in your browser, you won't find any references to the PHP3 code. The PHP3 module does not pass it on to the end user's browser. Only the output (if there is any) of the script is sent to the browser. So if you embed usernames and passwords in the code, this will not be seen by the end user.

One of the problems with CGI applications is Variable Scope. In other words, if a variable is declared in a particular piece of PHP3 script, where can that variable be accessed? How are variables passed from one page to another? This is answered in the next section.

A Word on Variable Scope

Here are the basic rules for accessing variables:

  • Any variable declared in a particular PHP3 document (eg "tutorial.php3") is available throughout that document.
  • If another document is included (using the PHP3 "include" function), then the variable is available inside the included document as well.
  • If a variable is declared inside an included document, then that variable is available in the outer document, in code that occurs after the include statement.
  • When a script has finished executing, then all variables vanish.
  • In PHP3 there is a way to make variables survive, and therefore be available in the next call to a PHP3 script, but this makes things overly complex in my opinion, and doesn't work well on Unix/Linux where there are typically a number of httpd daemons running, and you may not connect to the same one each time.
  • PHP4 implements sessions, which may make this easier. However, at time of writing, PHP4 had not yet been released.

So, if a script is invoked, and it declares a bunch of variables, and displays a page of HTML, and one of the links in this page invokes another script, then the variables from the first script are not available in the next script. If you need to pass values from one page to another, then pass these values in the URL, or use the standard HTML FORM with a POST button. Both of these methods are used in the sample application. Note that when a page is POSTed, only the normal HTML INPUT boxes etc are passed to the new page, not any PHP3 variables. However, the receiving page can access the passed values as ordinary PHP3 variables. So if we had a simple form as follows:

   <FORM METHOD="post" ACTION="page2.php3?SECTIONNO=3">
   <INPUT TYPE="text" name="NAME" size="50" >
   <INPUT TYPE ="submit" Value="Submit" name="submitbtn">

Then the receiving script (page2.php3) would have access to two variables, $SECTIONNO with a value of 3, and $NAME with a value of whatever had been typed in. The former was passed in the URL, while the latter was POSTed.

PHP3 Interbase API Reference

I have included a link to the PHP3 Interbase API. I have put it in a separate document in order to keep this one tidy.

Here is the Interbase API for PHP3.

Connecting to Interbase

There are two functions that can be used to connect to interbase:

   ibase_connect
   ibase_pconnect

The first version creates a new connection to the database, while the second creates a persistent connection to the database. When ibase_pconnect is executed, PHP3 looks to see if there is already a similar connection available, and connects to that. If no similar connection is found, then a new one is created.

Apache on Linux typically has a number of httpd daemons running to service incoming requests. Each process therefore will have it's own persistent connections, which will remain open until Apache is stopped or re-started. I have encountered problems with the Interbase connection timing out when connecting via the network. So if PHP3 was to connect to localhost:/somedir/some.gdb I found that the connection had timed out after a few minutes, and either PHP3 would pause for a long while, hang, or an error would be returned.

In the sample application, all connections use ibase_pconnect, and the connections are to a local database, so the timeout is not encountered.

NOTE: that since connections remain open, extra care must be taken to ensure that transactions are completed, otherwise long running transactions will create performance problems and record locking problems.

Each php3 page must reconnect to the database, either by using ibase_connect or ibase_pconnect. In the sample application, each page includes a file called header.php3, which displays the common header information. Header.php3 in turn includes checklogin.php3, which is there to ensure that each page is being accessed by a logged in browser. The first thing that checklogin.php3 does is to connect (with ibase_pconnect) to the database. This means that header.php3 has a valid connection, and in turn the page that included header.php3 also has a valid connection to the database.

Interbase Transactions in PHP3

The Interbase support within PHP3 allows for a default transaction. Each function that works within a transaction, such as ibase_query will work within the default transaction unless told otherwise. If the default transaction has not been started, then these functions will start it. Here is a fragment of code that uses the default transaction:

<?php
   ibase_pconnect("/home/wwwroots/synectics/php3/employee.gdb","sysdba", "masterkey" ); 
   $Q = ibase_query("select retval,user_name from checklogin(?,?)",$LOGINID,$IP);
   $R = ibase_fetch_row($Q);
   $USER_NAME = $R[1];
   ibase_free_result($Q);
   ibase_commit();
?>

The default transaction will be sufficient for most purposes. If you need to have a number of different transactions open, then look at ibase_transibase_query and Default Transaction.

Executing Queries

There are two main methods of executing queries within PHP3. The simplest is to use ibase_query. This allows you to define the query either with or without parameters, and at the same time, supply the values to the parameters. Here is a small sample:

<?php
   ibase_pconnect("/home/wwwroots/synectics/php3/employee.gdb","sysdba", "masterkey" ); 
   $LOGINID = "frank";
   $IP = $REMOTE_ADDR;
   $Q = ibase_query("select retval,user_name from checklogin(?,?)",$LOGINID,$IP);
   $R = ibase_fetch_row($Q);
   $USER_NAME = $R[1];
   ibase_free_result($Q);
   ibase_commit();
?>
Here the two parameters are supplied with values. This means that it is easy to run the same query with different values each time. For the most part, queries are executed once each time a page is called, so this is the easiest way to execute queries. If, however, a query needs to be run many times each time a page is executed, inside a loop for example, then it would be better to use the second method of executing queries, which uses two functions: ibase_prepare and ibase_execute. Here is a sample of this method:
<?php
   
   ibase_pconnect("/home/wwwroots/synectics/php3/employee.gdb","sysdba", "masterkey" ); 
   $Q = ibase_prepare("select total from salary where unique_id = ?");
   $TOTAL = 0;
   for ($I=1;$I < 10;$I++)
   {
       $R = ibase_execute($Q,$I);
       $ROW = ibase_fetch_row($R);
       $TOTAL += $ROW[0];
   }
   ibase_free_result($R);
   ibase_commit();
?>

This is much more efficient than putting an ibase_query within the loop, since the prepare phase is a very expensive operation. In fact, for short efficient queries, the prepare phase may take twice as long as the execute.

Authenticating Users

There are a number of ways to authenticate users using HTML, HTTP and so on. My sample does not use any of these methods, but rather implements a simple way of verifying users via passwords and IP Addresses.

Simply put, once the user logs in, a loginid is generated. This loginid is passed from page to page, and the first thing that each page does, is check in the database whether this loginid is still current, and whether the page has been requested by the same IP Address as originally logged in. If no activity has been noted for a particular loginid for 10 minutes, then it expires, and the user is requested to log in again.

If the loginid is noted to come from a different IP Address, i.e. someone borrowed the URL from someone else, then access is denied for all pages, and the IP Address can be logged, and action can be taken.

Like most authentication systems, this one is secure, as long as the passwords are kept secret, and as long as the web server and database servers are secure in themselves.

I created a Stored Procedure which does most of the work:

   create procedure checklogin (loginid integer,ipaddress varchar(15))
   returns (retval integer,user_name varchar(60)) 
   as
   declare variable tmpipaddress varchar(15);
   begin
   /*retval = 0 = OK
    retval = 1 = expired or invalid id
    retval = 2 = wrong ipaddress
   */

   begin
   delete from logins
   where lastaccess < (cast('now' as date) - 0.007);
   when any do
     tmpipaddress = tmpipaddress;
   end

   retval = 1;
   tmpipaddress = "";

   select ipaddress,user_name
     from logins
    where loginid = :loginid
     into :tmpipaddress,:user_name;

   if (tmpipaddress = "") then
      retval = 1; /*expired or invalid id */
   else if (tmpipaddress = ipaddress) then
   begin
     retval = 0; /*O.K. valid login from correct ipaddress*/
     update logins set lastaccess = 'now' where loginid = :loginid;
     when any do
       tmpipaddress = tmpipaddress;
   end
   else
    retval = 2; /*someone is trying with a valid loginid, but with an invalid ipaddress*/
    
   suspend;

   end^

The first thing this SP does, is to delete any logins that are older than 10 minutes (0.007 of a day). Since this is the first thing that happens when retrieving any page, this effectively means that once a loginid is older than 10 minutes, the user is forced to log in again. This also means that attempted hacks into expired logns will not be seen as a hack attempt, but will merely be treated as an expired login. The principle could be modified to mark the login as expired, rather than deleting it. This will also allow better access analysis to be done, but the table could grow very large on a busy site.

Each page has an include("header.php3") at the top. This calls include("checklogin.php3"), which returns whether the login is valid or not. Depending on this, an appropriate message is displayed, or the page is displayed. Here is the source to header.php3:


header.php3


<?php /*This header file is included at the top of all pages except the first, since the first does not do any validation. Each page is passed the LOGINID, and each page must first check whether this is a valid login id from the same IP Address, and it has not timed out. */ echo "<HTML> <TITLE> Employee System </TITLE> <BODY BGCOLOR= "white">"; if (!isset($LOGINID)) $LOGINID = -1; # CHECKLOGIN check that the LOGINID is valid, and it also makes sure that each page # gets connected to the database */ include ("checklogin.php3"); if ($VALIDLOGIN == 2) { echo "<BR><BR><BR><BR><BR><BR> <CENTER><H1><FONT color="red"> An illegal intrusion has been noted from IP Address <BR> $REMOTE_ADDR <BR> $REMOTE_HOST <BR> This has been logged in the Intrusion Database! <BR> Please go <A HREF="page1.php3">here</A> to login.</FONT></H1></CENTER>"; } if ($VALIDLOGIN == 0) { echo "<BR><BR><BR><BR><BR><BR><CENTER><H1><FONT color="blue">Your Session has expired. Please go the <A HREF="page1.php3">Log On</A> page to log on again.</FONT></H1></CENTER>"; echo "<CENTER><H3>This happens when your session has been inactive for more than 10 minutes, and is a security measure to prevent unauthorised viewing of your data.</H3></CENTER>"; } ?>

You will see that it first calls checklogin.php3 and then depending on the value returned, displays an appropriate error, or falls out, and the relevent page displays. Here is the code to checklogin.php3:


checklogin.php3


<?php $VALIDLOGIN = 0; ibase_pconnect("/home/wwwroots/synectics/php3/employee.gdb","sysdba", "masterkey" ); if (ibase_errmsg()) { echo "error: " . ibase_errmsg(); exit; } $IP = $REMOTE_ADDR; $Q = ibase_query("select retval,user_name from checklogin(?,?)",$LOGINID,$IP); $R = ibase_fetch_row($Q); $RETVAL = $R[0]; $USER_NAME = $R[1]; ibase_free_result($Q); ibase_commit(); /*Please excuse the clumsiness here: left hand not knowing what the right is doing :-) */ if ($RETVAL == 0) { $VALIDLOGIN = 1; } else if ($RETVAL == 2) { $VALIDLOGIN = 2; } else if ($RETVAL==1) { $VALIDLOGIN = 0; } ?>

Displaying the Data

Displaying data is pretty straight-forward. If the data contains HTML text, or if it is text that needs no formatting, then just use the echo function to pump it through to the browser. Another common way of displaying data retrieved from a table is inside an HTML table. This is quite easy: simply create the table with the correct HTML tags, and in a loop print the data inside the row (<TR>) and data (<TD>) tags . Here is a snippet of code that does this:

echo "
<TABLE >
<TR>
<TH>Emp No</TH>
<TH>Name </TH>
<TH>Dept No </TH>
";


   $Q = ibase_query("select empno,surname,department from employee");
   while ($R = ibase_fetch_row($Q)) 
   {
      echo "
      <TR>
          <TD> $R[0]</TD>
          <TD> $R[1]</TD>
          <TD> $R[2]</TD>
      </TR>

     ";
       
   }
   ibase_free_result($Q);
   ibase_commit();

echo "
</TABLE>";

Displaying Text Blobs

For the most part, text blobs need to be displayed as is, i.e. no further formatting is required. If the text has already been formatted with HTML, then it's straight-forward. If not, then it's probably best to display the text within simple HTML formatting tags, such as a TABLE or inside a TEXTAREA box.

There are two ways to retrieve Blob data, for both text or binary data. Both require the blob to be retrieved from the database with a query. The first uses the ibase_blob_echo function, which will be demonstrated in this section, while the second fetches the Blob data into a local variable, and then displayed from the local variable. This is demonstrated in the next section Displaying Binary blobs. The second method is very useful for text Blobs as well.

Here is a snippet of code, which retrieves a text Blob, and echos it to the browser:

echo "<TABLE>";

     $S =  "select proj_name,proj_desc from EMPLOYEE_PROJECT pe, project p ";
     $S .= "where pe.emp_no = ? and p.proj_id = pe.proj_id";
     $Q1 = ibase_query($S,$EMPNO);
     while ($R1 = ibase_fetch_row($Q1))
     {
        echo "
        <TR><TD>Project Name</TD>
            <TD>$R1[0]</TD></TR>
        <TR><TD>Project Desc</TD>
            <TD>";

            ibase_blob_echo($R1[1]);

         echo "</TD></TR>";
     }

echo "</TABLE>

Here, the Blob is echoed out between the table data tags, and appears inside a cell in the table.

Displaying Binary Blobs

There are two ways of 'displaying' binary data. 'Displaying' may be the wrong term to use, since often the data is not visual, but could be audio, which you want to play in the client browser.

The first way is to use the PHP function header which is an HTTP function. This tells the browser what the format is of the streamed data. Headers need to be sent before any other output, and so it is not clear how to embed a streamed image into a formatted page.

The second way is a bit more clumsy, and requires temporary files, but gives more control. This method is demonstrated here, by showing how to retrieve an image and display it.

We use a standard query to select the data, but when fetching the data, we use the ibase_fetch_object rather than t3506bhe ibase_fetch_row functi3506bon. This allows us to retrieve the blob with the ibase_blob_open and ibase_blob_get functions.

The retrieved blob is then written to a file, and then displayed in the browser with a standard <IMG> tag.

The ibase_blob_get function fetches the blob in chunks, so must be used in a loop to ensure that the whole blob is retrieved.

The PHP function tempnam is used to create a temporary file. This filename needs to be modified slightly since the tempnam function can create a file anywhere on any file system, while the <IMG> tag will only serve images below the document root of the web server. In other words, absolute pathnames do not work the same way for tempnam and <IMG>.

Here is some code to do this.

   $S =  "select emp_no,hire_date,salary,pic,full_name";
   $S .= " from employee m join user_permissions p ";
   $S .= "  on p.dept_no = m.dept_no ";
   $S .= "  where p.user_name = ? ";
   $S .= "  and m.emp_no = ?";

   $Q = ibase_query($S,$USER_NAME,$EMPNO);
   while ($R = ibase_fetch_object($Q))
   {
     $TEMPFILE = tempnam("temp","TMP");
     $TEMPFILE .= ".jpg";
        
     $BLOBID = ibase_blob_open($R->PIC);
     $FILEID = fopen($TEMPFILE,"w");

//Loop thru the blob, fetching 10K chunks, and write it to the temp file
     while ($PIC = ibase_blob_get($BLOBID,10240))
     {
        fputs($FILEID,$PIC);    
     }
     fclose($FILEID);
     ibase_blob_close($BLOBID);
     $TEMPFILE = "/php3/" . $TEMPFILE;
     echo "<BR><BR><CENTER><IMG SRC=$TEMPFILE ></CENTER>";

   }
 
   ibase_free_result($Q);
   ibase_commit();

Updating Tables in the Database

Updating a table in a database, is as simple as executing an Insert or Update statement.

In general when writing a web page which requires user input then a FORM is used with TEXT and TEXTAREA input areas. Javascript can also be used to provide simple validation on the client side, so that invalid data is not sent to the database. However, all data must be re-validated on the server side, either inside the PHP script, or within database triggers, preferably both.

The sample application allows the user to edit an employee's salary. So, all data is displayed in an ordinary table format, and the salary is displayed inside a TEXT input box. When the user hits the 'Change Salary' button, the data is posted to a PHP3 script which updates the database.

Here is the script which updates the data. All variables such as $EBSALARY were posted to this page. The only slightly tricky thing is to trap any errors and display them, otherwise it couldn't be simpler:

<?php
include("header.php3");

if ($VALIDLOGIN == 1)
{

$Q = ibase_query("update employee set salary = ? where emp_no = ?",$EBSALARY, $EBEMPNO);
if (ibase_errmsg())
{
   echo "<FONT color ="red"><h2>Employee $EBEMPNO not updated due to the following error:</h2></font><br>";
   echo "Error: " . ibase_errmsg();
}
else
{
   echo "<CENTER><H2>Employee $EBEMPNO salary changed to $EBSALARY</H2></CENTER>";
}

ibase_commit();

include ("trailer.php3");
}
?>

Server Response from: ETNASC04